Skip to content

Commit 2ab5d40

Browse files
committed
Added userid to mcp
1 parent 14e3fb6 commit 2ab5d40

1 file changed

Lines changed: 54 additions & 20 deletions

File tree

backend/src/ai/mcp.ts

Lines changed: 54 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,13 @@ import { q, all_async } from '../core/db'
99
import { getEmbeddingInfo } from '../memory/embed'
1010
import { j, p } from '../utils'
1111
import type { sector_type, mem_row, rpc_err_code } from '../core/types'
12+
import { update_user_summary } from '../memory/user_summary'
1213

1314
const sec_enum = z.enum(['episodic', 'semantic', 'procedural', 'emotional', 'reflective'] as const)
1415

1516
const trunc = (val: string, max = 200) => val.length <= max ? val : `${val.slice(0, max).trimEnd()}...`
1617

17-
const build_mem_snap = (row: mem_row) => ({ id: row.id, primary_sector: row.primary_sector, salience: Number(row.salience.toFixed(3)), last_seen_at: row.last_seen_at, content_preview: trunc(row.content, 240) })
18+
const build_mem_snap = (row: mem_row) => ({ id: row.id, primary_sector: row.primary_sector, salience: Number(row.salience.toFixed(3)), last_seen_at: row.last_seen_at, user_id: row.user_id, content_preview: trunc(row.content, 240) })
1819

1920
const fmt_matches = (matches: Awaited<ReturnType<typeof hsg_query>>) => matches.map((m: any, idx: any) => {
2021
const prev = trunc(m.content.replace(/\s+/g, ' ').trim(), 200)
@@ -36,16 +37,24 @@ const send_err = (res: ServerResponse, code: rpc_err_code, msg: string, id: numb
3637
}
3738
}
3839

40+
const uid = (val?: string | null) => val?.trim() ? val.trim() : undefined
41+
3942
export const create_mcp_srv = () => {
4043
const srv = new McpServer({ name: 'openmemory-mcp', version: '2.1.0', protocolVersion: '2025-06-18' }, { capabilities: { tools: {}, resources: {}, logging: {} } })
4144

4245
srv.tool('openmemory.query', 'Run a semantic retrieval against OpenMemory', {
4346
query: z.string().min(1, 'query text is required').describe('Free-form search text'),
4447
k: z.number().int().min(1).max(32).default(8).describe('Maximum results to return'),
4548
sector: sec_enum.optional().describe('Restrict search to a specific sector'),
46-
min_salience: z.number().min(0).max(1).optional().describe('Minimum salience threshold')
47-
}, async ({ query, k, sector, min_salience }) => {
48-
const flt = { sectors: sector ? [sector as sector_type] : undefined, minSalience: min_salience }
49+
min_salience: z.number().min(0).max(1).optional().describe('Minimum salience threshold'),
50+
user_id: z.string().trim().min(1).optional().describe('Isolate results to a specific user identifier')
51+
}, async ({ query, k, sector, min_salience, user_id }) => {
52+
const u = uid(user_id)
53+
const flt = sector || min_salience !== undefined || u ? {
54+
...(sector ? { sectors: [sector as sector_type] } : {}),
55+
...(min_salience !== undefined ? { minSalience: min_salience } : {}),
56+
...(u ? { user_id: u } : {})
57+
} : undefined
4958
const matches = await hsg_query(query, k ?? 8, flt)
5059
const summ = matches.length ? fmt_matches(matches) : 'No memories matched the supplied query.'
5160
const pay = matches.map((m: any) => ({ id: m.id, score: Number(m.score.toFixed(4)), primary_sector: m.primary_sector, sectors: m.sectors, salience: Number(m.salience.toFixed(4)), last_seen_at: m.last_seen_at, path: m.path, content: m.content }))
@@ -55,11 +64,15 @@ export const create_mcp_srv = () => {
5564
srv.tool('openmemory.store', 'Persist new content into OpenMemory', {
5665
content: z.string().min(1).describe('Raw memory text to store'),
5766
tags: z.array(z.string()).optional().describe('Optional tag list'),
58-
metadata: z.record(z.any()).optional().describe('Arbitrary metadata blob')
59-
}, async ({ content, tags, metadata }) => {
60-
const res = await add_hsg_memory(content, j(tags || []), metadata)
61-
const txt = `Stored memory ${res.id} (primary=${res.primary_sector}) across sectors: ${res.sectors.join(', ')}`
62-
return { content: [{ type: 'text', text: txt }, { type: 'text', text: JSON.stringify({ id: res.id, primary_sector: res.primary_sector, sectors: res.sectors }, null, 2) }] }
67+
metadata: z.record(z.any()).optional().describe('Arbitrary metadata blob'),
68+
user_id: z.string().trim().min(1).optional().describe('Associate the memory with a specific user identifier')
69+
}, async ({ content, tags, metadata, user_id }) => {
70+
const u = uid(user_id)
71+
const res = await add_hsg_memory(content, j(tags || []), metadata, u)
72+
if (u) update_user_summary(u).catch(err => console.error('[MCP] user summary update failed:', err))
73+
const txt = `Stored memory ${res.id} (primary=${res.primary_sector}) across sectors: ${res.sectors.join(', ')}${u ? ` [user=${u}]` : ''}`
74+
const payload = { id: res.id, primary_sector: res.primary_sector, sectors: res.sectors, user_id: u ?? null }
75+
return { content: [{ type: 'text', text: txt }, { type: 'text', text: JSON.stringify(payload, null, 2) }] }
6376
})
6477

6578
srv.tool('openmemory.reinforce', 'Boost salience for an existing memory', {
@@ -72,25 +85,46 @@ export const create_mcp_srv = () => {
7285

7386
srv.tool('openmemory.list', 'List recent memories for quick inspection', {
7487
limit: z.number().int().min(1).max(50).default(10).describe('Number of memories to return'),
75-
sector: sec_enum.optional().describe('Optionally limit to a sector')
76-
}, async ({ limit, sector }) => {
77-
const rows: mem_row[] = sector ? await q.all_mem_by_sector.all(sector, limit ?? 10, 0) : await q.all_mem.all(limit ?? 10, 0)
88+
sector: sec_enum.optional().describe('Optionally limit to a sector'),
89+
user_id: z.string().trim().min(1).optional().describe('Restrict results to a specific user identifier')
90+
}, async ({ limit, sector, user_id }) => {
91+
const u = uid(user_id)
92+
let rows: mem_row[]
93+
if (u) {
94+
const all = await q.all_mem_by_user.all(u, limit ?? 10, 0)
95+
rows = sector ? all.filter(row => row.primary_sector === sector) : all
96+
} else {
97+
rows = sector ? await q.all_mem_by_sector.all(sector, limit ?? 10, 0) : await q.all_mem.all(limit ?? 10, 0)
98+
}
7899
const items = rows.map(row => ({ ...build_mem_snap(row), tags: p(row.tags || '[]') as string[], metadata: p(row.meta || '{}') as Record<string, unknown> }))
79-
const lns = items.map((item, idx) => {
80-
const tag_str = item.tags.length ? ` tags=${item.tags.join(', ')}` : ''
81-
return `${idx + 1}. [${item.primary_sector}] salience=${item.salience} id=${item.id}${tag_str}\n${item.content_preview}`
82-
})
100+
const lns = items.map((item, idx) => `${idx + 1}. [${item.primary_sector}] salience=${item.salience} id=${item.id}${item.tags.length ? ` tags=${item.tags.join(', ')}` : ''}${item.user_id ? ` user=${item.user_id}` : ''}\n${item.content_preview}`)
83101
return { content: [{ type: 'text', text: lns.join('\n\n') || 'No memories stored yet.' }, { type: 'text', text: JSON.stringify({ items }, null, 2) }] }
84102
})
85103

86104
srv.tool('openmemory.get', 'Fetch a single memory by identifier', {
87105
id: z.string().min(1).describe('Memory identifier to load'),
88-
include_vectors: z.boolean().default(false).describe('Include sector vector metadata')
89-
}, async ({ id, include_vectors }) => {
106+
include_vectors: z.boolean().default(false).describe('Include sector vector metadata'),
107+
user_id: z.string().trim().min(1).optional().describe('Validate ownership against a specific user identifier')
108+
}, async ({ id, include_vectors, user_id }) => {
109+
const u = uid(user_id)
90110
const mem = await q.get_mem.get(id)
91111
if (!mem) return { content: [{ type: 'text', text: `Memory ${id} not found.` }] }
112+
if (u && mem.user_id !== u) return { content: [{ type: 'text', text: `Memory ${id} not found for user ${u}.` }] }
92113
const vecs = include_vectors ? await q.get_vecs_by_id.all(id) : []
93-
const pay = { id: mem.id, content: mem.content, primary_sector: mem.primary_sector, salience: mem.salience, decay_lambda: mem.decay_lambda, created_at: mem.created_at, updated_at: mem.updated_at, last_seen_at: mem.last_seen_at, tags: p(mem.tags || '[]'), metadata: p(mem.meta || '{}'), sectors: include_vectors ? vecs.map(v => v.sector) : undefined }
114+
const pay = {
115+
id: mem.id,
116+
content: mem.content,
117+
primary_sector: mem.primary_sector,
118+
salience: mem.salience,
119+
decay_lambda: mem.decay_lambda,
120+
created_at: mem.created_at,
121+
updated_at: mem.updated_at,
122+
last_seen_at: mem.last_seen_at,
123+
user_id: mem.user_id,
124+
tags: p(mem.tags || '[]'),
125+
metadata: p(mem.meta || '{}'),
126+
sectors: include_vectors ? vecs.map(v => v.sector) : undefined
127+
}
94128
return { content: [{ type: 'text', text: JSON.stringify(pay, null, 2) }] }
95129
})
96130

@@ -161,4 +195,4 @@ export const start_mcp_stdio = async () => {
161195

162196
if (typeof require !== 'undefined' && require.main === module) {
163197
void start_mcp_stdio().catch(error => { console.error('[MCP] STDIO startup failed:', error); process.exitCode = 1 })
164-
}
198+
}

0 commit comments

Comments
 (0)