@raviolelabs/engram-mcp 0.2.2 → 0.4.2

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.
Files changed (64) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +139 -134
  3. package/SKILL.md +33 -0
  4. package/dist/core/db/index.d.ts.map +1 -1
  5. package/dist/core/db/index.js +27 -1
  6. package/dist/core/db/index.js.map +1 -1
  7. package/dist/core/logger.d.ts.map +1 -1
  8. package/dist/core/logger.js +4 -4
  9. package/dist/core/logger.js.map +1 -1
  10. package/dist/core/server/http.d.ts +3 -0
  11. package/dist/core/server/http.d.ts.map +1 -1
  12. package/dist/core/server/http.js +6 -0
  13. package/dist/core/server/http.js.map +1 -1
  14. package/dist/core/server/instructions.js +1 -1
  15. package/dist/core/server/mcp-handler.d.ts.map +1 -1
  16. package/dist/core/server/mcp-handler.js +28 -1
  17. package/dist/core/server/mcp-handler.js.map +1 -1
  18. package/dist/embeddings/index.d.ts +6 -0
  19. package/dist/embeddings/index.d.ts.map +1 -1
  20. package/dist/embeddings/index.js +31 -3
  21. package/dist/embeddings/index.js.map +1 -1
  22. package/dist/embeddings/providers/engram.d.ts +10 -0
  23. package/dist/embeddings/providers/engram.d.ts.map +1 -1
  24. package/dist/embeddings/providers/engram.js +35 -12
  25. package/dist/embeddings/providers/engram.js.map +1 -1
  26. package/dist/mcp-server/tests/mcp-e2e.test.js +1 -1
  27. package/dist/mcp-server/tests/mcp-e2e.test.js.map +1 -1
  28. package/dist/memory/core/store.d.ts.map +1 -1
  29. package/dist/memory/core/store.js +5 -4
  30. package/dist/memory/core/store.js.map +1 -1
  31. package/dist/memory/modules/team/keystore.d.ts +38 -0
  32. package/dist/memory/modules/team/keystore.d.ts.map +1 -0
  33. package/dist/memory/modules/team/keystore.js +117 -0
  34. package/dist/memory/modules/team/keystore.js.map +1 -0
  35. package/dist/memory/modules/team/tools.d.ts +10 -0
  36. package/dist/memory/modules/team/tools.d.ts.map +1 -0
  37. package/dist/memory/modules/team/tools.js +252 -0
  38. package/dist/memory/modules/team/tools.js.map +1 -0
  39. package/dist/memory/public/tools.d.ts.map +1 -1
  40. package/dist/memory/public/tools.js +33 -5
  41. package/dist/memory/public/tools.js.map +1 -1
  42. package/dist/server/api/memories.d.ts.map +1 -1
  43. package/dist/server/api/memories.js +79 -8
  44. package/dist/server/api/memories.js.map +1 -1
  45. package/dist/server/api/types.d.ts +5 -0
  46. package/dist/server/api/types.d.ts.map +1 -1
  47. package/dist/server/api/types.js +90 -2
  48. package/dist/server/api/types.js.map +1 -1
  49. package/dist/sync/team-sync.d.ts +19 -0
  50. package/dist/sync/team-sync.d.ts.map +1 -0
  51. package/dist/sync/team-sync.js +80 -0
  52. package/dist/sync/team-sync.js.map +1 -0
  53. package/dist/tests/scope-encryption.test.d.ts +2 -0
  54. package/dist/tests/scope-encryption.test.d.ts.map +1 -0
  55. package/dist/tests/scope-encryption.test.js +71 -0
  56. package/dist/tests/scope-encryption.test.js.map +1 -0
  57. package/dist/types.d.ts +5 -1
  58. package/dist/types.d.ts.map +1 -1
  59. package/dist/webapp/api/team.d.ts +7 -0
  60. package/dist/webapp/api/team.d.ts.map +1 -0
  61. package/dist/webapp/api/team.js +57 -0
  62. package/dist/webapp/api/team.js.map +1 -0
  63. package/package.json +2 -1
  64. package/CLAUDE.md +0 -232
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../../../src/memory/modules/team/tools.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAY9D,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,YAAY,CAAC;IACrB,SAAS,EAAE,UAAU,CAAC;IACtB,sDAAsD;IACtD,MAAM,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CACtC;AAED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,qBAAqB,GAAG,iBAAiB,EAAE,CAyRnF"}
@@ -0,0 +1,252 @@
1
+ // src/memory/modules/team/tools.ts
2
+ // Workspace MCP tools: list_workspaces, create_workspace, leave_workspace,
3
+ // invite_to_workspace, accept_workspace_invite.
4
+ // These are registered alongside the public tools.
5
+ import sodium from 'libsodium-wrappers';
6
+ import { createLogger } from '../../../logger.js';
7
+ import { generateWorkspaceKey, wrapKeyForRecipient, unwrapAndStoreWorkspaceKey, deleteWorkspaceKey, getOrCreateX25519Keypair, getX25519PubkeyHex, } from './keystore.js';
8
+ const log = createLogger('team:tools');
9
+ export function buildWorkspaceTools(ctx) {
10
+ const cloudUrl = ctx.config.cloudBaseUrl
11
+ ?? 'https://api.engram-mcp.com';
12
+ const dataDir = ctx.config.dataDir;
13
+ async function cloudFetch(path, init = {}) {
14
+ const jwt = await ctx.getJwt();
15
+ const headers = {
16
+ 'Content-Type': 'application/json',
17
+ ...(init.headers ?? {}),
18
+ };
19
+ if (jwt)
20
+ headers['Cookie'] = `engram_session=${jwt}`;
21
+ return fetch(`${cloudUrl}${path}`, { ...init, headers });
22
+ }
23
+ return [
24
+ // ── list_workspaces ───────────────────────────────────────────────────────
25
+ {
26
+ name: 'list_workspaces',
27
+ description: [
28
+ 'Return all team workspaces the current user belongs to.',
29
+ 'WHEN: you need workspace IDs for use with scope parameter, or to show the user their teams.',
30
+ 'Returns personal workspace plus all team workspaces with name, role, and workspace_id.',
31
+ 'RETURNS: { workspaces: [{ id, name, role, is_personal }] }.',
32
+ ].join(' '),
33
+ inputSchema: { type: 'object', properties: {} },
34
+ handler: async () => {
35
+ const { getDb } = await import('../../../db/index.js');
36
+ const db = getDb();
37
+ const rows = db
38
+ .prepare('SELECT id, name, role, owner_email, joined_at FROM workspaces ORDER BY joined_at ASC')
39
+ .all();
40
+ return {
41
+ workspaces: [
42
+ { id: 'personal', name: 'Personal', role: 'owner', is_personal: true },
43
+ ...rows.map((r) => ({
44
+ id: r.id,
45
+ name: r.name,
46
+ role: r.role,
47
+ is_personal: false,
48
+ owner_email: r.owner_email,
49
+ joined_at: r.joined_at,
50
+ })),
51
+ ],
52
+ };
53
+ },
54
+ },
55
+ // ── create_workspace ──────────────────────────────────────────────────────
56
+ {
57
+ name: 'create_workspace',
58
+ description: [
59
+ 'Create a new team workspace. Generates a team master key locally, wraps it for the owner, and registers with engram-cloud.',
60
+ 'WHEN: user wants to start a shared team memory space.',
61
+ 'After creation, use invite_to_workspace to add members.',
62
+ 'Requires an active cloud session (engram pair must have been run).',
63
+ 'RETURNS: { workspace_id, name } or { error }.',
64
+ ].join(' '),
65
+ inputSchema: {
66
+ type: 'object',
67
+ properties: {
68
+ name: {
69
+ type: 'string',
70
+ description: 'Display name for the workspace (e.g. "Acme Research").',
71
+ },
72
+ },
73
+ required: ['name'],
74
+ },
75
+ handler: async (args) => {
76
+ const name = args.name.trim();
77
+ if (!name)
78
+ return { error: 'name_required' };
79
+ await sodium.ready;
80
+ const keypair = await getOrCreateX25519Keypair(dataDir, ctx.masterKey);
81
+ const ownerPubkeyHex = await getX25519PubkeyHex(dataDir);
82
+ if (!ownerPubkeyHex)
83
+ return { error: 'keypair_not_found', hint: 'Run engram pair first.' };
84
+ // Generate fresh team master key + wrap it for the owner
85
+ const teamKey = await generateWorkspaceKey();
86
+ const wrappedForOwner = await wrapKeyForRecipient(teamKey, ownerPubkeyHex);
87
+ // Create workspace on cloud
88
+ const res = await cloudFetch('/api/workspaces', {
89
+ method: 'POST',
90
+ body: JSON.stringify({ name, wrapped_team_key: wrappedForOwner }),
91
+ });
92
+ if (!res.ok) {
93
+ const err = await res.json().catch(() => ({}));
94
+ log.error('create_workspace cloud error', err);
95
+ return { error: err.error ?? `cloud_error_${res.status}`, message: err.message };
96
+ }
97
+ const data = await res.json();
98
+ // Store team master key + register workspace locally
99
+ const { getDb } = await import('../../../db/index.js');
100
+ const db = getDb();
101
+ const now = new Date().toISOString();
102
+ db.prepare(`INSERT OR REPLACE INTO workspaces (id, name, role, joined_at) VALUES (?, ?, 'owner', ?)`).run(data.id, data.name, now);
103
+ // Persist decrypted team key
104
+ await unwrapAndStoreWorkspaceKey(dataDir, data.id, wrappedForOwner, keypair);
105
+ log.info(`Created workspace ${data.id} "${data.name}"`);
106
+ return { workspace_id: data.id, name: data.name };
107
+ },
108
+ },
109
+ // ── leave_workspace ───────────────────────────────────────────────────────
110
+ {
111
+ name: 'leave_workspace',
112
+ description: [
113
+ 'Remove yourself from a team workspace. Deletes the local team key.',
114
+ 'WHEN: user wants to leave a team or the workspace is being decommissioned.',
115
+ 'Requires confirm: true to prevent accidental removal.',
116
+ 'RETURNS: { ok: true } or { error }.',
117
+ ].join(' '),
118
+ inputSchema: {
119
+ type: 'object',
120
+ properties: {
121
+ workspace_id: { type: 'string', description: 'Workspace id to leave.' },
122
+ confirm: {
123
+ type: 'boolean',
124
+ description: 'Must be true to confirm leaving. Prevents accidental removal.',
125
+ },
126
+ },
127
+ required: ['workspace_id', 'confirm'],
128
+ },
129
+ handler: async (args) => {
130
+ if (!args.confirm) {
131
+ return { error: 'confirm_required', message: 'Pass confirm: true to leave the workspace.' };
132
+ }
133
+ const workspaceId = args.workspace_id;
134
+ // Remove from local SQLite registry
135
+ const { getDb } = await import('../../../db/index.js');
136
+ const db = getDb();
137
+ db.prepare('DELETE FROM workspaces WHERE id = ?').run(workspaceId);
138
+ // Delete local team key
139
+ await deleteWorkspaceKey(dataDir, workspaceId);
140
+ log.info(`Left workspace ${workspaceId}`);
141
+ return { ok: true };
142
+ },
143
+ },
144
+ // ── invite_to_workspace ───────────────────────────────────────────────────
145
+ {
146
+ name: 'invite_to_workspace',
147
+ description: [
148
+ 'Invite a user to a team workspace by email. Fetches their X25519 pubkey from cloud, wraps the team key for them, and sends an invitation email.',
149
+ 'WHEN: workspace owner or admin wants to add a new member.',
150
+ 'The invitee must have a paired engram-mcp device (their pubkey must be registered).',
151
+ 'RETURNS: { ok: true, invitation_id } or { error }.',
152
+ ].join(' '),
153
+ inputSchema: {
154
+ type: 'object',
155
+ properties: {
156
+ workspace_id: { type: 'string', description: 'Workspace id (from list_workspaces).' },
157
+ email: { type: 'string', description: 'Email address of the invitee.' },
158
+ role: {
159
+ type: 'string',
160
+ enum: ['member', 'admin'],
161
+ description: 'Role to assign. Default: "member".',
162
+ default: 'member',
163
+ },
164
+ },
165
+ required: ['workspace_id', 'email'],
166
+ },
167
+ handler: async (args) => {
168
+ const workspaceId = args.workspace_id;
169
+ const email = args.email.trim().toLowerCase();
170
+ const role = args.role ?? 'member';
171
+ await sodium.ready;
172
+ const keypair = await getOrCreateX25519Keypair(dataDir, ctx.masterKey);
173
+ // Load local team key
174
+ const { loadWorkspaceKey } = await import('./keystore.js');
175
+ const teamKey = await loadWorkspaceKey(dataDir, workspaceId);
176
+ if (!teamKey) {
177
+ return { error: 'team_key_not_found', message: 'You do not have a local key for this workspace. Are you the owner?' };
178
+ }
179
+ // Fetch invitee's pubkey from cloud
180
+ const pubkeyRes = await cloudFetch(`/api/users/pubkey?email=${encodeURIComponent(email)}`);
181
+ if (!pubkeyRes.ok) {
182
+ const err = await pubkeyRes.json().catch(() => ({}));
183
+ return { error: err.error ?? 'pubkey_fetch_failed', message: err.message };
184
+ }
185
+ const { x25519_pubkey: inviteePubkeyHex } = await pubkeyRes.json();
186
+ // Wrap team key for invitee
187
+ const wrappedForInvitee = await wrapKeyForRecipient(teamKey, inviteePubkeyHex);
188
+ // Send invitation
189
+ const invRes = await cloudFetch(`/api/workspaces/${workspaceId}/invite`, {
190
+ method: 'POST',
191
+ body: JSON.stringify({ email, role, wrapped_team_key: wrappedForInvitee }),
192
+ });
193
+ if (!invRes.ok) {
194
+ const err = await invRes.json().catch(() => ({}));
195
+ return { error: err.error ?? 'invite_failed', message: err.message };
196
+ }
197
+ const data = await invRes.json();
198
+ log.info(`Invited ${email} to workspace ${workspaceId}`);
199
+ return { ok: true, invitation_id: data.invitation_id };
200
+ void keypair; // keypair loaded to ensure keys are ready — not used directly for wrapping
201
+ },
202
+ },
203
+ // ── accept_workspace_invite ───────────────────────────────────────────────
204
+ {
205
+ name: 'accept_workspace_invite',
206
+ description: [
207
+ 'Accept a team workspace invitation using the token from the invitation email.',
208
+ 'Decrypts the wrapped team key using your local X25519 private key and stores it.',
209
+ 'WHEN: you received an invitation link and want to join a team workspace.',
210
+ 'RETURNS: { ok: true, workspace_id } or { error }.',
211
+ ].join(' '),
212
+ inputSchema: {
213
+ type: 'object',
214
+ properties: {
215
+ token: { type: 'string', description: 'Invitation token from the email link.' },
216
+ },
217
+ required: ['token'],
218
+ },
219
+ handler: async (args) => {
220
+ const token = args.token;
221
+ await sodium.ready;
222
+ const keypair = await getOrCreateX25519Keypair(dataDir, ctx.masterKey);
223
+ const res = await cloudFetch('/api/workspaces/invitations/accept', {
224
+ method: 'POST',
225
+ body: JSON.stringify({ token }),
226
+ });
227
+ if (!res.ok) {
228
+ const err = await res.json().catch(() => ({}));
229
+ return { error: err.error ?? 'accept_failed', message: err.message };
230
+ }
231
+ const data = await res.json();
232
+ if (data.wrapped_team_key) {
233
+ await unwrapAndStoreWorkspaceKey(dataDir, data.workspace_id, data.wrapped_team_key, keypair);
234
+ }
235
+ // Register workspace locally
236
+ const workspaceInfoRes = await cloudFetch('/api/workspaces');
237
+ if (workspaceInfoRes.ok) {
238
+ const { workspaces } = await workspaceInfoRes.json();
239
+ const ws = workspaces.find((w) => w.id === data.workspace_id);
240
+ if (ws) {
241
+ const { getDb } = await import('../../../db/index.js');
242
+ const db = getDb();
243
+ db.prepare(`INSERT OR REPLACE INTO workspaces (id, name, role, joined_at) VALUES (?, ?, ?, ?)`).run(ws.id, ws.name, ws.role, ws.joined_at);
244
+ }
245
+ }
246
+ log.info(`Accepted invite for workspace ${data.workspace_id}`);
247
+ return { ok: true, workspace_id: data.workspace_id };
248
+ },
249
+ },
250
+ ];
251
+ }
252
+ //# sourceMappingURL=tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.js","sourceRoot":"","sources":["../../../../src/memory/modules/team/tools.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../../src/memory/public/tools.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAsB3D,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,YAAY,GAAG,iBAAiB,EAAE,CAs+C9F"}
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../../src/memory/public/tools.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAsB3D,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,YAAY,GAAG,iBAAiB,EAAE,CAmgD9F"}
@@ -30,6 +30,7 @@ export function buildPublicTools(store, config) {
30
30
  description: [
31
31
  'Store a memory (note, audio transcript, conversation, document) for later semantic retrieval.',
32
32
  'INPUTS: content (required, free text). Optionally provide title (improves retrieval — 3-7 words), tags (array of strings — topics/people/projects this memory is about), type (default "notes"; use "conversations" for dialog turns, or any custom type), and properties (object — any extra key/value metadata).',
33
+ 'SCOPE: use scope="personal" (default) for private memories, or scope="workspace:<id>" to store in a team workspace (get id from list_workspaces).',
33
34
  'WHEN: call after user shares anything worth remembering (preferences, facts, decisions, exchanges). Always include title + 2-5 tags so it surfaces in future recall.',
34
35
  'WIKILINKS: mention related memories by [[id]] or [[title]] in content — edges are auto-extracted.',
35
36
  'IDEMPOTENT on (content_hash, type): calling twice with identical content returns the same id with {created: false}.',
@@ -58,6 +59,11 @@ export function buildPublicTools(store, config) {
58
59
  description: 'Memory type: "notes" (default), "conversations", or any custom type.',
59
60
  default: 'notes',
60
61
  },
62
+ scope: {
63
+ type: 'string',
64
+ description: 'Visibility scope: "personal" (default, private to you) or "workspace:<id>" for a team workspace.',
65
+ default: 'personal',
66
+ },
61
67
  properties: {
62
68
  type: 'object',
63
69
  description: 'Optional extra metadata: source_url, author, sentiment, action_required, custom fields.',
@@ -70,6 +76,7 @@ export function buildPublicTools(store, config) {
70
76
  const title = args.title;
71
77
  const tags = args.tags;
72
78
  const type = args.type ?? 'notes';
79
+ const scope = args.scope ?? 'personal';
73
80
  const extraProps = args.properties ?? {};
74
81
  const contentHash = createHash('sha256').update(content).digest('hex');
75
82
  // Idempotency: check for existing memory with same content_hash + type
@@ -87,6 +94,7 @@ export function buildPublicTools(store, config) {
87
94
  const item = {
88
95
  id: ulid(),
89
96
  type,
97
+ scope,
90
98
  source_id: `manual:${Date.now()}`,
91
99
  content,
92
100
  content_hash: contentHash,
@@ -125,11 +133,12 @@ export function buildPublicTools(store, config) {
125
133
  description: [
126
134
  'Retrieve past memories by semantic similarity to a query.',
127
135
  'INPUTS: query (required, short topic — NOT the full user question), optional types (array to restrict scope), optional limit (default 10, max 50).',
136
+ 'SCOPE: use scope="personal" (default) to search only personal memories, scope="workspace:<id>" for a specific team workspace, or scope="all" to search across personal + all joined workspaces.',
128
137
  'WHEN: you need to surface past information on a topic. Use recall instead of recent when you have a specific subject in mind.',
129
138
  'TIP: extract the topic noun from the user message. "what did I say about alice?" → query: "alice".',
130
139
  'ANTI-LOOP: if results is empty, the answer is genuinely "no matching memories" — DO NOT call recall again with the same query.',
131
140
  'Try a different query (synonym, broader topic, related entity) at most twice before telling the user "no matches".',
132
- 'RETURNS: array of { id, type, score, snippet, title, tags, created_at }.',
141
+ 'RETURNS: array of { id, type, score, snippet, title, tags, created_at, scope }.',
133
142
  ].join(' '),
134
143
  inputSchema: {
135
144
  type: 'object',
@@ -143,6 +152,11 @@ export function buildPublicTools(store, config) {
143
152
  items: { type: 'string' },
144
153
  description: 'Restrict to these memory types (default: all). Faster when type is known.',
145
154
  },
155
+ scope: {
156
+ type: 'string',
157
+ description: '"personal" (default), "workspace:<id>", or "all" (personal + all workspaces).',
158
+ default: 'personal',
159
+ },
146
160
  limit: {
147
161
  type: 'number',
148
162
  default: 10,
@@ -368,10 +382,11 @@ export function buildPublicTools(store, config) {
368
382
  name: 'recent',
369
383
  description: [
370
384
  'Return the most recently created memories, sorted by created_at desc.',
385
+ 'SCOPE: use scope="personal" (default) for private memories, scope="workspace:<id>" for team memories, or scope="all" for all scopes.',
371
386
  'WHEN: start of a conversation — call recent({limit: 10}) to refresh context before answering.',
372
387
  'Use recall instead of recent when you have a specific topic in mind.',
373
388
  'ANTI-LOOP: returns at most limit items (default 20). If fewer returned, the store has fewer memories — DO NOT call again with a larger limit hoping for more.',
374
- 'RETURNS: array of { id, type, title, tags, snippet, created_at }.',
389
+ 'RETURNS: array of { id, type, title, tags, snippet, created_at, scope }.',
375
390
  ].join(' '),
376
391
  inputSchema: {
377
392
  type: 'object',
@@ -386,20 +401,32 @@ export function buildPublicTools(store, config) {
386
401
  items: { type: 'string' },
387
402
  description: 'Restrict to these memory types (optional).',
388
403
  },
404
+ scope: {
405
+ type: 'string',
406
+ description: '"personal" (default), "workspace:<id>", or "all" (personal + all workspaces).',
407
+ default: 'personal',
408
+ },
389
409
  },
390
410
  },
391
411
  handler: async (args) => {
392
412
  const limit = args.limit ?? 20;
393
413
  const types = args.types;
414
+ const scope = args.scope ?? 'personal';
394
415
  const { getDb } = await import('../../db/index.js');
395
416
  const db = getDb();
396
- let sql = `SELECT id, type, content, properties_json, created_at
397
- FROM memories`;
417
+ const conditions = [];
398
418
  const params = [];
399
419
  if (types && types.length > 0) {
400
- sql += ` WHERE type IN (${types.map(() => '?').join(',')})`;
420
+ conditions.push(`type IN (${types.map(() => '?').join(',')})`);
401
421
  params.push(...types);
402
422
  }
423
+ if (scope !== 'all') {
424
+ conditions.push('scope = ?');
425
+ params.push(scope);
426
+ }
427
+ let sql = `SELECT id, type, content, properties_json, created_at, scope FROM memories`;
428
+ if (conditions.length > 0)
429
+ sql += ` WHERE ${conditions.join(' AND ')}`;
403
430
  sql += ` ORDER BY created_at DESC LIMIT ?`;
404
431
  params.push(limit);
405
432
  const rows = db.prepare(sql).all(...params);
@@ -408,6 +435,7 @@ export function buildPublicTools(store, config) {
408
435
  return {
409
436
  id: r.id,
410
437
  type: r.type,
438
+ scope: r.scope ?? 'personal',
411
439
  title: props.title,
412
440
  tags: props.tags,
413
441
  snippet: r.content.slice(0, 200),