@knowall-ai/mcp-neo4j-agent-memory
Version:
Graph-based memory system for AI agents. Store people, places, organizations as nodes. Build semantic relationships (KNOWS, WORKS_AT, CREATED). Word-tokenized search finds partial matches. Filter by date, traverse relationships, maintain temporal context.
114 lines (113 loc) • 4.16 kB
JavaScript
import neo4j, { Node, Relationship, Integer } from 'neo4j-driver';
export class Neo4jClient {
driver;
database;
constructor(uri, username, password, database) {
this.driver = neo4j.driver(uri, neo4j.auth.basic(username, password));
this.database = database;
}
convertNestedIntegers(value) {
if (value instanceof Integer) {
return value.toNumber();
}
if (Array.isArray(value)) {
return value.map(item => this.convertNestedIntegers(item));
}
if (value && typeof value === 'object' && value.constructor === Object) {
const converted = {};
for (const [key, val] of Object.entries(value)) {
converted[key] = this.convertNestedIntegers(val);
}
return converted;
}
return value;
}
async executeQuery(query, params = {}) {
const session = this.driver.session({
database: this.database
});
try {
const result = await session.run(query, params);
return result.records.map((record) => {
const obj = {};
for (const key of record.keys) {
const value = record.get(key);
if (value instanceof Node || value instanceof Relationship) {
obj[key] = {
...this.convertNestedIntegers(value.properties),
_id: value.identity.toNumber(),
_labels: value instanceof Node ? value.labels : undefined,
_type: value instanceof Relationship ? value.type : undefined,
};
}
else {
obj[key] = this.convertNestedIntegers(value);
}
}
return obj;
});
}
finally {
await session.close();
}
}
async getNodes(label) {
return this.executeQuery(`MATCH (n:${label}) RETURN n as memory`);
}
async createNode(label, properties) {
const result = await this.executeQuery(`CREATE (n:${label} $props) RETURN n as memory`, { props: properties });
return result[0];
}
async createRelationship(fromNodeId, toNodeId, relationType, properties = {}) {
const result = await this.executeQuery(`MATCH (a), (b)
WHERE id(a) = $fromId AND id(b) = $toId
CREATE (a)-[r:${relationType} $props]->(b)
RETURN r as relationship`, {
fromId: neo4j.int(fromNodeId),
toId: neo4j.int(toNodeId),
props: properties,
});
return result[0];
}
async updateNode(nodeId, properties) {
const result = await this.executeQuery(`MATCH (n) WHERE id(n) = $nodeId
SET n += $props
RETURN n as memory`, {
nodeId: neo4j.int(nodeId),
props: properties,
});
return result[0];
}
async updateRelationship(fromNodeId, toNodeId, relationType, properties) {
const result = await this.executeQuery(`MATCH (a)-[r:${relationType}]->(b)
WHERE id(a) = $fromId AND id(b) = $toId
SET r += $props
RETURN r as relationship`, {
fromId: neo4j.int(fromNodeId),
toId: neo4j.int(toNodeId),
props: properties,
});
return result[0];
}
async deleteNode(nodeId) {
const result = await this.executeQuery(`MATCH (n) WHERE id(n) = $nodeId
DETACH DELETE n
RETURN count(n) as deletedCount`, {
nodeId: neo4j.int(nodeId),
});
return result[0];
}
async deleteRelationship(fromNodeId, toNodeId, relationType) {
const result = await this.executeQuery(`MATCH (a)-[r:${relationType}]->(b)
WHERE id(a) = $fromId AND id(b) = $toId
DELETE r
RETURN count(r) as deletedCount`, {
fromId: neo4j.int(fromNodeId),
toId: neo4j.int(toNodeId),
});
return result[0];
}
async close() {
await this.driver.close();
}
}