@iwo-szapar/data-mcp 0.5.0 → 0.6.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.
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * Adapter factory — creates the correct DataAdapter based on configuration.
3
3
  *
4
- * Always wraps the adapter in SchemaMapProxy (transparent if no mappings).
4
+ * Wraps adapters in SchemaMapProxy and optional OwnerScopeProxy.
5
5
  */
6
6
  import type { Config } from '../config.js';
7
7
  import type { DataAdapter } from './types.js';
8
8
  export declare function createAdapter(config: Config): DataAdapter;
9
- //# sourceMappingURL=factory.d.ts.map
9
+ //# sourceMappingURL=factory.d.ts.map
@@ -1,12 +1,13 @@
1
1
  /**
2
2
  * Adapter factory — creates the correct DataAdapter based on configuration.
3
3
  *
4
- * Always wraps the adapter in SchemaMapProxy (transparent if no mappings).
4
+ * Wraps adapters in SchemaMapProxy and optional OwnerScopeProxy.
5
5
  */
6
6
  import { PocketBaseAdapter } from './pocketbase.js';
7
7
  import { SupabaseAdapter } from './supabase.js';
8
8
  import { MarkdownAdapter } from './markdown.js';
9
9
  import { SchemaMap, SchemaMapProxy } from './schema-map.js';
10
+ import { OwnerScopeProxy } from './owner-scope.js';
10
11
  export function createAdapter(config) {
11
12
  let adapter;
12
13
  if (config.backend === 'pocketbase') {
@@ -20,8 +21,11 @@ export function createAdapter(config) {
20
21
  }
21
22
  const schemaMap = new SchemaMap(config.schemaMap);
22
23
  if (!schemaMap.isEmpty) {
23
- return new SchemaMapProxy(adapter, schemaMap);
24
+ adapter = new SchemaMapProxy(adapter, schemaMap);
25
+ }
26
+ if (config.backend !== 'pocketbase' && config.ownerRouting) {
27
+ adapter = new OwnerScopeProxy(adapter, config.ownerRouting);
24
28
  }
25
29
  return adapter;
26
30
  }
27
- //# sourceMappingURL=factory.js.map
31
+ //# sourceMappingURL=factory.js.map
@@ -0,0 +1,31 @@
1
+ import type { DataAdapter, Filter, ListResult, PageOptions, SortClause } from './types.js';
2
+ export interface OwnerRoutingConfig {
3
+ ownerId: string;
4
+ sharedOwnerId: string;
5
+ }
6
+ export declare class OwnerScopeProxy implements DataAdapter {
7
+ private inner;
8
+ private ownerId;
9
+ private sharedOwnerId;
10
+ readonly ownerScopeEnabled = true;
11
+ constructor(inner: DataAdapter, config: OwnerRoutingConfig);
12
+ get backend(): 'pocketbase' | 'supabase' | 'markdown';
13
+ create<T extends Record<string, unknown>>(collection: string, data: Record<string, unknown>): Promise<T>;
14
+ getOne<T extends Record<string, unknown>>(collection: string, id: string): Promise<T>;
15
+ list<T extends Record<string, unknown>>(collection: string, options?: {
16
+ filter?: Filter;
17
+ sort?: SortClause[];
18
+ page?: PageOptions;
19
+ }): Promise<ListResult<T>>;
20
+ textSearch<T extends Record<string, unknown>>(collection: string, query: string, options?: {
21
+ fields?: string[];
22
+ filter?: Filter;
23
+ limit?: number;
24
+ }): Promise<T[]>;
25
+ update<T extends Record<string, unknown>>(collection: string, id: string, data: Record<string, unknown>): Promise<T>;
26
+ delete(collection: string, id: string): Promise<void>;
27
+ upsert<T extends Record<string, unknown>>(collection: string, data: Record<string, unknown>, uniqueFields: string[]): Promise<T>;
28
+ count(collection: string, filter?: Filter): Promise<number>;
29
+ collectionExists(collection: string): Promise<boolean>;
30
+ listCollections(): Promise<string[]>;
31
+ }
@@ -0,0 +1,155 @@
1
+ import { AdapterError } from '../errors/adapter-error.js';
2
+
3
+ const DEFAULT_SCOPED_COLLECTIONS = new Set([
4
+ 'knowledge',
5
+ 'decisions',
6
+ 'sessions',
7
+ 'goals',
8
+ 'tasks',
9
+ 'contacts',
10
+ 'knowledge_links',
11
+ ]);
12
+
13
+ export class OwnerScopeProxy {
14
+ inner;
15
+ ownerId;
16
+ sharedOwnerId;
17
+ ownerScopeEnabled = true;
18
+ constructor(inner, config) {
19
+ this.inner = inner;
20
+ this.ownerId = config.ownerId;
21
+ this.sharedOwnerId = config.sharedOwnerId;
22
+ }
23
+ get backend() {
24
+ return this.inner.backend;
25
+ }
26
+ async create(collection, data) {
27
+ return this.inner.create(collection, this.withWriteOwner(collection, data));
28
+ }
29
+ async getOne(collection, id) {
30
+ const record = await this.inner.getOne(collection, id);
31
+ this.assertReadable(collection, record);
32
+ return record;
33
+ }
34
+ async list(collection, options) {
35
+ return this.inner.list(collection, this.withReadFilter(collection, options));
36
+ }
37
+ async textSearch(collection, query, options) {
38
+ return this.inner.textSearch(collection, query, this.withReadFilter(collection, options));
39
+ }
40
+ async update(collection, id, data) {
41
+ await this.getOne(collection, id);
42
+ return this.inner.update(collection, id, this.withUpdateOwner(collection, data));
43
+ }
44
+ async delete(collection, id) {
45
+ await this.getOne(collection, id);
46
+ return this.inner.delete(collection, id);
47
+ }
48
+ async upsert(collection, data, uniqueFields) {
49
+ const scopedData = this.withWriteOwner(collection, data);
50
+ const scopedUniqueFields = this.isScoped(collection) && !uniqueFields.includes('owner_id')
51
+ ? [...uniqueFields, 'owner_id']
52
+ : uniqueFields;
53
+ return this.inner.upsert(collection, scopedData, scopedUniqueFields);
54
+ }
55
+ async count(collection, filter) {
56
+ return this.inner.count(collection, this.ownerFilter(collection, filter));
57
+ }
58
+ async collectionExists(collection) {
59
+ return this.inner.collectionExists(collection);
60
+ }
61
+ async listCollections() {
62
+ return this.inner.listCollections();
63
+ }
64
+ isScoped(collection) {
65
+ return DEFAULT_SCOPED_COLLECTIONS.has(collection);
66
+ }
67
+ visibleOwners() {
68
+ return this.ownerId === this.sharedOwnerId
69
+ ? [this.sharedOwnerId]
70
+ : [this.ownerId, this.sharedOwnerId];
71
+ }
72
+ ownerFilter(collection, filter) {
73
+ if (!this.isScoped(collection))
74
+ return filter;
75
+ const groups = filter && filter.length > 0 ? filter : [[]];
76
+ return groups.map((andGroup) => {
77
+ let requestedOwners;
78
+ const cleanGroup = [];
79
+ for (const clause of andGroup) {
80
+ if (clause.field === 'owner_scope') {
81
+ requestedOwners = this.resolveReadOwners(clause.value);
82
+ continue;
83
+ }
84
+ cleanGroup.push(clause);
85
+ }
86
+ const owners = requestedOwners ?? this.visibleOwners();
87
+ const ownerClause = owners.length === 1
88
+ ? { field: 'owner_id', op: 'eq', value: owners[0] }
89
+ : { field: 'owner_id', op: 'in', value: owners };
90
+ return [...cleanGroup, ownerClause];
91
+ });
92
+ }
93
+ withReadFilter(collection, options) {
94
+ if (!this.isScoped(collection))
95
+ return options;
96
+ return { ...options, filter: this.ownerFilter(collection, options?.filter) };
97
+ }
98
+ withWriteOwner(collection, data) {
99
+ if (!this.isScoped(collection))
100
+ return stripOwnerScope(data);
101
+ const ownerId = this.resolveWriteOwner(data);
102
+ return { ...stripOwnerScope(data), owner_id: ownerId };
103
+ }
104
+ withUpdateOwner(collection, data) {
105
+ if (!this.isScoped(collection))
106
+ return stripOwnerScope(data);
107
+ if (!hasOwnerIntent(data))
108
+ return stripOwnerScope(data);
109
+ const ownerId = this.resolveWriteOwner(data);
110
+ return { ...stripOwnerScope(data), owner_id: ownerId };
111
+ }
112
+ resolveWriteOwner(data) {
113
+ const ownerScope = typeof data.owner_scope === 'string' ? data.owner_scope : undefined;
114
+ if (ownerScope === 'shared')
115
+ return this.sharedOwnerId;
116
+ if (ownerScope === 'private')
117
+ return this.ownerId;
118
+ const requestedOwner = typeof data.owner_id === 'string' ? data.owner_id.trim() : '';
119
+ if (!requestedOwner)
120
+ return this.ownerId;
121
+ if (requestedOwner === this.ownerId || requestedOwner === this.sharedOwnerId)
122
+ return requestedOwner;
123
+ throw new AdapterError('VALIDATION_ERROR', 'owner_id must be the current owner or shared owner');
124
+ }
125
+ resolveReadOwners(value) {
126
+ const scopes = Array.isArray(value) ? value : [value];
127
+ const owners = [];
128
+ for (const scope of scopes) {
129
+ if (scope === 'private')
130
+ owners.push(this.ownerId);
131
+ else if (scope === 'shared')
132
+ owners.push(this.sharedOwnerId);
133
+ else
134
+ throw new AdapterError('VALIDATION_ERROR', 'owner_scope must be private or shared');
135
+ }
136
+ return [...new Set(owners)];
137
+ }
138
+ assertReadable(collection, record) {
139
+ if (!this.isScoped(collection))
140
+ return;
141
+ if (this.visibleOwners().includes(record.owner_id))
142
+ return;
143
+ throw new AdapterError('RECORD_NOT_FOUND', `Record not found in '${collection}'`);
144
+ }
145
+ }
146
+
147
+ function stripOwnerScope(data) {
148
+ const { owner_scope: _ownerScope, ...clean } = data;
149
+ return clean;
150
+ }
151
+
152
+ function hasOwnerIntent(data) {
153
+ return Object.prototype.hasOwnProperty.call(data, 'owner_scope')
154
+ || Object.prototype.hasOwnProperty.call(data, 'owner_id');
155
+ }
@@ -63,5 +63,6 @@ export interface DataAdapter {
63
63
  collectionExists(collection: string): Promise<boolean>;
64
64
  /** List all collection names */
65
65
  listCollections(): Promise<string[]>;
66
+ readonly ownerScopeEnabled?: boolean;
66
67
  }
67
- //# sourceMappingURL=types.d.ts.map
68
+ //# sourceMappingURL=types.d.ts.map
package/dist/config.d.ts CHANGED
@@ -1,16 +1,19 @@
1
1
  /**
2
2
  * Configuration parsing from environment variables.
3
3
  *
4
- * SB_BACKEND: 'pocketbase' | 'supabase' (required)
4
+ * SB_BACKEND: 'pocketbase' | 'supabase' | 'markdown' (required)
5
5
  * SB_POCKETBASE_URL: PocketBase server URL (required if backend=pocketbase)
6
6
  * SB_POCKETBASE_ADMIN_EMAIL: PocketBase admin email (required if backend=pocketbase)
7
7
  * SB_POCKETBASE_ADMIN_PASSWORD: PocketBase admin password (required if backend=pocketbase)
8
8
  * SB_SUPABASE_URL: Supabase project URL (required if backend=supabase)
9
9
  * SB_SUPABASE_KEY: Supabase service role key (required if backend=supabase)
10
+ * SB_MARKDOWN_ROOT: filesystem path to the memory/ folder (required if backend=markdown)
10
11
  * SB_SCHEMA_MAP: JSON string mapping logical names to actual table names (optional)
11
12
  * SB_RESEND_API_KEY: Resend API key for email sending (optional)
13
+ * MEMORYOS_OWNER_ID: current memory owner id, enables owner routing when set (optional)
14
+ * MEMORYOS_SHARED_OWNER_ID: shared/team memory owner id (optional, default: firma)
12
15
  */
13
- export type Backend = 'pocketbase' | 'supabase';
16
+ export type Backend = 'pocketbase' | 'supabase' | 'markdown';
14
17
  export interface PocketBaseConfig {
15
18
  backend: 'pocketbase';
16
19
  pocketbaseUrl: string;
@@ -18,6 +21,7 @@ export interface PocketBaseConfig {
18
21
  pocketbaseAdminPassword: string;
19
22
  schemaMap: Record<string, string>;
20
23
  resendApiKey?: string;
24
+ ownerRouting?: OwnerRoutingConfig;
21
25
  }
22
26
  export interface SupabaseConfig {
23
27
  backend: 'supabase';
@@ -25,7 +29,19 @@ export interface SupabaseConfig {
25
29
  supabaseKey: string;
26
30
  schemaMap: Record<string, string>;
27
31
  resendApiKey?: string;
32
+ ownerRouting?: OwnerRoutingConfig;
28
33
  }
29
- export type Config = PocketBaseConfig | SupabaseConfig;
34
+ export interface MarkdownConfig {
35
+ backend: 'markdown';
36
+ markdownRoot: string;
37
+ schemaMap: Record<string, string>;
38
+ resendApiKey?: string;
39
+ ownerRouting?: OwnerRoutingConfig;
40
+ }
41
+ export interface OwnerRoutingConfig {
42
+ ownerId: string;
43
+ sharedOwnerId: string;
44
+ }
45
+ export type Config = PocketBaseConfig | SupabaseConfig | MarkdownConfig;
30
46
  export declare function parseConfig(): Config;
31
- //# sourceMappingURL=config.d.ts.map
47
+ //# sourceMappingURL=config.d.ts.map
package/dist/config.js CHANGED
@@ -10,6 +10,8 @@
10
10
  * SB_MARKDOWN_ROOT: filesystem path to the memory/ folder (required if backend=markdown)
11
11
  * SB_SCHEMA_MAP: JSON string mapping logical names to actual table names (optional)
12
12
  * SB_RESEND_API_KEY: Resend API key for email sending (optional)
13
+ * MEMORYOS_OWNER_ID: current memory owner id, enables owner routing when set (optional)
14
+ * MEMORYOS_SHARED_OWNER_ID: shared/team memory owner id (optional, default: firma)
13
15
  */
14
16
  const ALLOWED_BACKENDS = ['pocketbase', 'supabase', 'markdown'];
15
17
  export function parseConfig() {
@@ -19,6 +21,7 @@ export function parseConfig() {
19
21
  }
20
22
  const schemaMap = parseSchemaMap(process.env.SB_SCHEMA_MAP);
21
23
  const resendApiKey = process.env.SB_RESEND_API_KEY || undefined;
24
+ const ownerRouting = parseOwnerRouting();
22
25
  if (backend === 'pocketbase') {
23
26
  return {
24
27
  backend,
@@ -27,6 +30,7 @@ export function parseConfig() {
27
30
  pocketbaseAdminPassword: requireEnv('SB_POCKETBASE_ADMIN_PASSWORD'),
28
31
  schemaMap,
29
32
  resendApiKey,
33
+ ownerRouting,
30
34
  };
31
35
  }
32
36
  if (backend === 'markdown') {
@@ -35,6 +39,7 @@ export function parseConfig() {
35
39
  markdownRoot: requireEnv('SB_MARKDOWN_ROOT'),
36
40
  schemaMap,
37
41
  resendApiKey,
42
+ ownerRouting,
38
43
  };
39
44
  }
40
45
  return {
@@ -43,6 +48,7 @@ export function parseConfig() {
43
48
  supabaseKey: requireEnv('SB_SUPABASE_KEY'),
44
49
  schemaMap,
45
50
  resendApiKey,
51
+ ownerRouting,
46
52
  };
47
53
  }
48
54
  function requireEnv(name) {
@@ -69,4 +75,11 @@ function parseSchemaMap(raw) {
69
75
  throw err;
70
76
  }
71
77
  }
72
- //# sourceMappingURL=config.js.map
78
+ function parseOwnerRouting() {
79
+ const ownerId = process.env.MEMORYOS_OWNER_ID?.trim();
80
+ if (!ownerId)
81
+ return undefined;
82
+ const sharedOwnerId = process.env.MEMORYOS_SHARED_OWNER_ID?.trim() || 'firma';
83
+ return { ownerId, sharedOwnerId };
84
+ }
85
+ //# sourceMappingURL=config.js.map
@@ -15,6 +15,7 @@ export function registerContactCreate(server, adapter) {
15
15
  relationship: z.enum(['colleague', 'client', 'prospect', 'partner', 'other']).optional().describe('Relationship type'),
16
16
  notes: z.string().max(5000).optional().describe('Notes about the contact'),
17
17
  tags: z.array(z.string().max(100)).max(20).optional().describe('Tags for categorization'),
18
+ owner_scope: z.enum(['private', 'shared']).optional().describe('Store privately for this user or in shared team memory'),
18
19
  }, withGracefulDegradation('contacts', adapter, async (params) => {
19
20
  try {
20
21
  const record = await adapter.create('contacts', {
@@ -26,6 +27,7 @@ export function registerContactCreate(server, adapter) {
26
27
  relationship: params.relationship ?? null,
27
28
  notes: params.notes ?? null,
28
29
  tags: params.tags ?? [],
30
+ ...(adapter.ownerScopeEnabled ? { owner_scope: params.owner_scope } : {}),
29
31
  last_contact_date: null,
30
32
  });
31
33
  return makeToolResponse({
@@ -39,4 +41,4 @@ export function registerContactCreate(server, adapter) {
39
41
  }
40
42
  }));
41
43
  }
42
- //# sourceMappingURL=contact-create.js.map
44
+ //# sourceMappingURL=contact-create.js.map
@@ -16,6 +16,7 @@ export function registerGoalCreate(server, adapter) {
16
16
  current: z.union([z.number(), z.string()]).optional(),
17
17
  })).optional().describe('Key results to track progress'),
18
18
  tags: z.array(z.string().max(100)).max(20).optional().describe('Tags for categorization'),
19
+ owner_scope: z.enum(['private', 'shared']).optional().describe('Store privately for this user or in shared team memory'),
19
20
  }, withGracefulDegradation('goals', adapter, async (params) => {
20
21
  try {
21
22
  const record = await adapter.create('goals', {
@@ -25,6 +26,7 @@ export function registerGoalCreate(server, adapter) {
25
26
  status: 'active',
26
27
  key_results: params.key_results ?? [],
27
28
  tags: params.tags ?? [],
29
+ ...(adapter.ownerScopeEnabled ? { owner_scope: params.owner_scope } : {}),
28
30
  });
29
31
  return makeToolResponse({
30
32
  created: true,
@@ -37,4 +39,4 @@ export function registerGoalCreate(server, adapter) {
37
39
  }
38
40
  }));
39
41
  }
40
- //# sourceMappingURL=goal-create.js.map
42
+ //# sourceMappingURL=goal-create.js.map
@@ -14,6 +14,7 @@ export function registerKnowledgeDecide(server, adapter) {
14
14
  chosen_option: z.string().min(1).max(500).describe('The option that was chosen'),
15
15
  rationale: z.string().max(5000).optional().describe('Why this option was chosen'),
16
16
  tags: z.array(z.string().max(100)).max(20).optional().describe('Tags for categorization'),
17
+ owner_scope: z.enum(['private', 'shared']).optional().describe('Store privately for this user or in shared team memory'),
17
18
  }, withGracefulDegradation('decisions', adapter, async (params) => {
18
19
  try {
19
20
  const record = await adapter.create('decisions', {
@@ -24,6 +25,7 @@ export function registerKnowledgeDecide(server, adapter) {
24
25
  rationale: params.rationale ?? null,
25
26
  outcome: null,
26
27
  tags: params.tags ?? [],
28
+ ...(adapter.ownerScopeEnabled ? { owner_scope: params.owner_scope } : {}),
27
29
  });
28
30
  return makeToolResponse({
29
31
  stored: true,
@@ -36,4 +38,4 @@ export function registerKnowledgeDecide(server, adapter) {
36
38
  }
37
39
  }));
38
40
  }
39
- //# sourceMappingURL=knowledge-decide.js.map
41
+ //# sourceMappingURL=knowledge-decide.js.map
@@ -13,6 +13,7 @@ export function registerKnowledgeLearn(server, adapter) {
13
13
  content: z.string().min(1).max(10000).describe('Detailed description of the learning'),
14
14
  tags: z.array(z.string().max(100)).max(20).optional().describe('Tags for categorization'),
15
15
  source: z.string().max(500).optional().describe('Context where this was learned'),
16
+ owner_scope: z.enum(['private', 'shared']).optional().describe('Store privately for this user or in shared team memory'),
16
17
  }, withGracefulDegradation('knowledge', adapter, async (params) => {
17
18
  try {
18
19
  const record = await adapter.create('knowledge', {
@@ -22,6 +23,7 @@ export function registerKnowledgeLearn(server, adapter) {
22
23
  summary: generateSummary(params.content),
23
24
  tags: params.tags ?? [],
24
25
  source: params.source ?? null,
26
+ ...(adapter.ownerScopeEnabled ? { owner_scope: params.owner_scope } : {}),
25
27
  confidence: 0.8,
26
28
  last_validated_at: new Date().toISOString(),
27
29
  });
@@ -36,4 +38,4 @@ export function registerKnowledgeLearn(server, adapter) {
36
38
  }
37
39
  }));
38
40
  }
39
- //# sourceMappingURL=knowledge-learn.js.map
41
+ //# sourceMappingURL=knowledge-learn.js.map
@@ -9,6 +9,7 @@ export function registerKnowledgeList(server, adapter) {
9
9
  server.tool('knowledge_list', 'List knowledge items with optional type and tag filters. Supports pagination.', {
10
10
  type: z.enum(['fact', 'pattern', 'insight', 'lesson', 'reference']).optional().describe('Filter by knowledge type'),
11
11
  tags: z.array(z.string()).optional().describe('Filter by tags (items must contain ALL specified tags)'),
12
+ owner_scope: z.enum(['private', 'shared']).optional().describe('Filter to private or shared team memory'),
12
13
  limit: z.number().int().min(1).max(100).optional().describe('Max results (default 20)'),
13
14
  offset: z.number().int().min(0).optional().describe('Offset for pagination (default 0)'),
14
15
  }, { readOnlyHint: true }, withGracefulDegradation('knowledge', adapter, async (params) => {
@@ -22,6 +23,9 @@ export function registerKnowledgeList(server, adapter) {
22
23
  clauses.push({ field: 'tags', op: 'contains', value: tag });
23
24
  }
24
25
  }
26
+ if (params.owner_scope && adapter.ownerScopeEnabled) {
27
+ clauses.push({ field: 'owner_scope', op: 'eq', value: params.owner_scope });
28
+ }
25
29
  const filter = clauses.length > 0 ? [clauses] : undefined;
26
30
  const result = await adapter.list('knowledge', {
27
31
  filter,
@@ -40,4 +44,4 @@ export function registerKnowledgeList(server, adapter) {
40
44
  }
41
45
  }));
42
46
  }
43
- //# sourceMappingURL=knowledge-list.js.map
47
+ //# sourceMappingURL=knowledge-list.js.map
@@ -12,15 +12,21 @@ export function registerKnowledgeRecall(server, adapter) {
12
12
  server.tool('knowledge_recall', 'Search your persistent memory. Full-text search across knowledge items and decisions. Empty query returns most recent items. Supports alias expansion (e.g., "payment" also finds "stripe").', {
13
13
  query: z.string().max(500).optional().describe('Search query (full-text search). Empty returns recent items.'),
14
14
  type: z.enum(['fact', 'pattern', 'insight', 'lesson', 'reference']).optional().describe('Filter by knowledge type'),
15
+ owner_scope: z.enum(['private', 'shared']).optional().describe('Filter to private or shared team memory'),
15
16
  limit: z.number().int().min(1).max(50).optional().describe('Max results (default 10)'),
16
17
  }, { readOnlyHint: true }, withGracefulDegradation('knowledge', adapter, async (params) => {
17
18
  try {
18
19
  const resultLimit = params.limit ?? 10;
19
20
  // Empty query: return most recent items
20
21
  if (!params.query || params.query.trim() === '') {
21
- const filter = params.type
22
- ? [[{ field: 'type', op: 'eq', value: params.type }]]
23
- : undefined;
22
+ const clauses = [];
23
+ if (params.type) {
24
+ clauses.push({ field: 'type', op: 'eq', value: params.type });
25
+ }
26
+ if (params.owner_scope && adapter.ownerScopeEnabled) {
27
+ clauses.push({ field: 'owner_scope', op: 'eq', value: params.owner_scope });
28
+ }
29
+ const filter = clauses.length > 0 ? [clauses] : undefined;
24
30
  const result = await adapter.list('knowledge', {
25
31
  filter,
26
32
  sort: [{ field: 'created_at', direction: 'desc' }],
@@ -38,9 +44,14 @@ export function registerKnowledgeRecall(server, adapter) {
38
44
  const expandedTerms = await expandQueryWithAliases(adapter, searchQuery);
39
45
  const expandedQuery = expandedTerms.join(' ');
40
46
  // Search knowledge
41
- const typeFilter = params.type
42
- ? [[{ field: 'type', op: 'eq', value: params.type }]]
43
- : undefined;
47
+ const clauses = [];
48
+ if (params.type) {
49
+ clauses.push({ field: 'type', op: 'eq', value: params.type });
50
+ }
51
+ if (params.owner_scope && adapter.ownerScopeEnabled) {
52
+ clauses.push({ field: 'owner_scope', op: 'eq', value: params.owner_scope });
53
+ }
54
+ const typeFilter = clauses.length > 0 ? [clauses] : undefined;
44
55
  const knowledgeResults = await adapter.textSearch('knowledge', expandedQuery, {
45
56
  fields: ['title', 'content', 'summary'],
46
57
  filter: typeFilter,
@@ -51,8 +62,12 @@ export function registerKnowledgeRecall(server, adapter) {
51
62
  try {
52
63
  const decisionsExist = await adapter.collectionExists('decisions');
53
64
  if (decisionsExist) {
65
+ const decisionFilter = params.owner_scope && adapter.ownerScopeEnabled
66
+ ? [[{ field: 'owner_scope', op: 'eq', value: params.owner_scope }]]
67
+ : undefined;
54
68
  decisionResults = await adapter.textSearch('decisions', expandedQuery, {
55
69
  fields: ['title', 'context', 'chosen_option'],
70
+ filter: decisionFilter,
56
71
  limit: 5,
57
72
  });
58
73
  }
@@ -76,4 +91,4 @@ export function registerKnowledgeRecall(server, adapter) {
76
91
  }
77
92
  }));
78
93
  }
79
- //# sourceMappingURL=knowledge-recall.js.map
94
+ //# sourceMappingURL=knowledge-recall.js.map
@@ -13,14 +13,19 @@ export function registerKnowledgeStore(server, adapter) {
13
13
  content: z.string().min(1).max(10000).describe('Content of the knowledge item'),
14
14
  tags: z.array(z.string().max(100)).max(20).optional().describe('Tags for categorization'),
15
15
  source: z.string().max(500).optional().describe('Source of the knowledge (URL, book, conversation, etc.)'),
16
+ owner_scope: z.enum(['private', 'shared']).optional().describe('Store privately for this user or in shared team memory'),
16
17
  }, withGracefulDegradation('knowledge', adapter, async (params) => {
17
18
  try {
18
19
  // Dedup: check for existing item with same type + title
20
+ const dedupFilter = [
21
+ { field: 'type', op: 'eq', value: params.type },
22
+ { field: 'title', op: 'eq', value: params.title.trim() },
23
+ ];
24
+ if (adapter.ownerScopeEnabled) {
25
+ dedupFilter.push({ field: 'owner_scope', op: 'eq', value: params.owner_scope ?? 'private' });
26
+ }
19
27
  const existing = await adapter.list('knowledge', {
20
- filter: [[
21
- { field: 'type', op: 'eq', value: params.type },
22
- { field: 'title', op: 'eq', value: params.title.trim() },
23
- ]],
28
+ filter: [dedupFilter],
24
29
  page: { limit: 1, offset: 0 },
25
30
  });
26
31
  if (existing.items.length > 0) {
@@ -39,6 +44,7 @@ export function registerKnowledgeStore(server, adapter) {
39
44
  summary: generateSummary(params.content),
40
45
  tags: params.tags ?? [],
41
46
  source: params.source ?? null,
47
+ ...(adapter.ownerScopeEnabled ? { owner_scope: params.owner_scope } : {}),
42
48
  confidence: 0.8,
43
49
  last_validated_at: new Date().toISOString(),
44
50
  });
@@ -53,4 +59,4 @@ export function registerKnowledgeStore(server, adapter) {
53
59
  }
54
60
  }));
55
61
  }
56
- //# sourceMappingURL=knowledge-store.js.map
62
+ //# sourceMappingURL=knowledge-store.js.map
@@ -18,6 +18,7 @@ export function registerLinkCreate(server, adapter) {
18
18
  relation_type: z.enum(RELATION_TYPES).describe('Type of relationship'),
19
19
  confidence: z.number().min(0).max(1).optional().default(0.8).describe('Confidence in this relationship (0-1)'),
20
20
  notes: z.string().max(500).optional().describe('Optional context for why this link exists'),
21
+ owner_scope: z.enum(['private', 'shared']).optional().describe('Store privately for this user or in shared team memory'),
21
22
  }, withGracefulDegradation('knowledge_links', adapter, async (params) => {
22
23
  try {
23
24
  if (params.source_type === params.target_type && params.source_id === params.target_id) {
@@ -26,14 +27,18 @@ export function registerLinkCreate(server, adapter) {
26
27
  message: 'Cannot create a self-link.',
27
28
  });
28
29
  }
30
+ const dedupFilter = [
31
+ { field: 'source_type', op: 'eq', value: params.source_type },
32
+ { field: 'source_id', op: 'eq', value: params.source_id },
33
+ { field: 'target_type', op: 'eq', value: params.target_type },
34
+ { field: 'target_id', op: 'eq', value: params.target_id },
35
+ { field: 'relation_type', op: 'eq', value: params.relation_type },
36
+ ];
37
+ if (adapter.ownerScopeEnabled) {
38
+ dedupFilter.push({ field: 'owner_scope', op: 'eq', value: params.owner_scope ?? 'private' });
39
+ }
29
40
  const existing = await adapter.list('knowledge_links', {
30
- filter: [[
31
- { field: 'source_type', op: 'eq', value: params.source_type },
32
- { field: 'source_id', op: 'eq', value: params.source_id },
33
- { field: 'target_type', op: 'eq', value: params.target_type },
34
- { field: 'target_id', op: 'eq', value: params.target_id },
35
- { field: 'relation_type', op: 'eq', value: params.relation_type },
36
- ]],
41
+ filter: [dedupFilter],
37
42
  page: { limit: 1, offset: 0 },
38
43
  });
39
44
  if (existing.items.length > 0) {
@@ -51,6 +56,7 @@ export function registerLinkCreate(server, adapter) {
51
56
  relation_type: params.relation_type,
52
57
  confidence: params.confidence ?? 0.8,
53
58
  notes: params.notes ?? null,
59
+ ...(adapter.ownerScopeEnabled ? { owner_scope: params.owner_scope } : {}),
54
60
  auto_suggested: false,
55
61
  });
56
62
  return makeToolResponse({
@@ -24,6 +24,7 @@ export function registerSessionLog(server, adapter) {
24
24
  })).optional().describe('Patterns learned during the session'),
25
25
  knowledge_created: z.number().int().min(0).optional().describe('Number of knowledge items created'),
26
26
  knowledge_updated: z.number().int().min(0).optional().describe('Number of knowledge items updated'),
27
+ owner_scope: z.enum(['private', 'shared']).optional().describe('Store privately for this user or in shared team memory'),
27
28
  metadata: z.record(z.unknown()).optional().describe('Additional metadata'),
28
29
  }, withGracefulDegradation('sessions', adapter, async (params) => {
29
30
  try {
@@ -40,6 +41,7 @@ export function registerSessionLog(server, adapter) {
40
41
  patterns_learned: params.patterns_learned ?? [],
41
42
  knowledge_created: params.knowledge_created ?? 0,
42
43
  knowledge_updated: params.knowledge_updated ?? 0,
44
+ ...(adapter.ownerScopeEnabled ? { owner_scope: params.owner_scope } : {}),
43
45
  metadata: params.metadata ?? null,
44
46
  });
45
47
  return makeToolResponse({
@@ -53,4 +55,4 @@ export function registerSessionLog(server, adapter) {
53
55
  }
54
56
  }));
55
57
  }
56
- //# sourceMappingURL=session-log.js.map
58
+ //# sourceMappingURL=session-log.js.map
@@ -13,6 +13,7 @@ export function registerTaskCreate(server, adapter) {
13
13
  due_date: z.string().max(50).optional().describe('Due date (ISO format)'),
14
14
  tags: z.array(z.string().max(100)).max(20).optional().describe('Tags for categorization'),
15
15
  goal_id: z.string().min(1).optional().describe('Related goal ID'),
16
+ owner_scope: z.enum(['private', 'shared']).optional().describe('Store privately for this user or in shared team memory'),
16
17
  }, withGracefulDegradation('tasks', adapter, async (params) => {
17
18
  try {
18
19
  const record = await adapter.create('tasks', {
@@ -23,6 +24,7 @@ export function registerTaskCreate(server, adapter) {
23
24
  due_date: params.due_date ?? null,
24
25
  tags: params.tags ?? [],
25
26
  goal_id: params.goal_id ?? null,
27
+ ...(adapter.ownerScopeEnabled ? { owner_scope: params.owner_scope } : {}),
26
28
  });
27
29
  return makeToolResponse({
28
30
  created: true,
@@ -35,4 +37,4 @@ export function registerTaskCreate(server, adapter) {
35
37
  }
36
38
  }));
37
39
  }
38
- //# sourceMappingURL=task-create.js.map
40
+ //# sourceMappingURL=task-create.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iwo-szapar/data-mcp",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Unified data MCP server for Second Brain — PocketBase and Supabase adapters. 40 tools: knowledge, sessions, goals, tasks, contacts, CRM, blog, email, content calendar.",
5
5
  "author": "Iwo Szapar <iwo.szapar@gmail.com> (https://iwoszapar.com)",
6
6
  "homepage": "https://iwoszapar.com/second-brain-ai",