Implemented two new MCP tools and enhanced workflow instructions to enable effective Personal Knowledge Management across all use cases (architecture decisions, projects, coaching, research, etc.). ## New Tools 1. **get_entity_connections** - Direct graph traversal showing ALL relationships - Returns complete connection data for an entity - Guarantees completeness vs semantic search - Essential for pattern detection and exploration - Leverages EntityEdge.get_by_node_uuid() 2. **get_entity_timeline** - Chronological episode history - Shows ALL episodes mentioning an entity - Enables temporal tracking and evolution analysis - Critical for understanding how concepts evolved - Leverages EpisodicNode.get_by_entity_node_uuid() ## Enhanced Workflow Instructions Updated GRAPHITI_MCP_INSTRUCTIONS with: - Clear "SEARCH FIRST, THEN ADD" workflow with decision flowcharts - Tool selection guide (when to use each tool) - Distinction between graph traversal vs semantic search - Multiple concrete examples across different domains - Key principles for effective PKM usage ## Updated add_memory Docstring Added prominent warning to search before adding: - Step-by-step workflow guidance - Emphasizes creating connections vs isolated nodes - References new exploration tools ## Benefits - Prevents disconnected/duplicate entities - Enables reliable pattern recognition with complete data - Cost-effective (single graph query vs multiple semantic searches) - Temporal tracking for evolution analysis - Works equally well for technical and personal knowledge ## Implementation Details - 0 changes to graphiti_core (uses existing features only) - All new code in mcp_server/src/graphiti_mcp_server.py - Backward compatible (adds tools, doesn't modify existing) - Follows existing MCP tool patterns and conventions - Passes all lint and syntax checks Related: DOCS/IMPLEMENTATION-Graph-Exploration-Tools.md
213 lines
6.9 KiB
Python
213 lines
6.9 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Migrate Graphiti data between databases and group_ids.
|
|
|
|
Usage:
|
|
python migrate_group_id.py
|
|
|
|
This script migrates data from:
|
|
Source: neo4j database, group_id='lvarming73'
|
|
Target: graphiti database, group_id='6910959f2128b5c4faa22283'
|
|
"""
|
|
|
|
import os
|
|
|
|
from neo4j import GraphDatabase
|
|
|
|
# Configuration
|
|
NEO4J_URI = 'bolt://192.168.1.25:7687'
|
|
NEO4J_USER = 'neo4j'
|
|
NEO4J_PASSWORD = os.environ.get('NEO4J_PASSWORD', '!"MiTa1205')
|
|
|
|
SOURCE_DATABASE = 'neo4j'
|
|
SOURCE_GROUP_ID = 'lvarming73'
|
|
|
|
TARGET_DATABASE = 'graphiti'
|
|
TARGET_GROUP_ID = '6910959f2128b5c4faa22283'
|
|
|
|
|
|
def migrate_data():
|
|
"""Migrate all nodes and relationships from source to target."""
|
|
|
|
driver = GraphDatabase.driver(NEO4J_URI, auth=(NEO4J_USER, NEO4J_PASSWORD))
|
|
|
|
try:
|
|
# Step 1: Export data from source database
|
|
print(
|
|
f'\n📤 Exporting data from {SOURCE_DATABASE} database (group_id: {SOURCE_GROUP_ID})...'
|
|
)
|
|
|
|
with driver.session(database=SOURCE_DATABASE) as session:
|
|
# Get all nodes with the source group_id
|
|
nodes_result = session.run(
|
|
"""
|
|
MATCH (n {group_id: $group_id})
|
|
RETURN
|
|
id(n) as old_id,
|
|
labels(n) as labels,
|
|
properties(n) as props
|
|
ORDER BY old_id
|
|
""",
|
|
group_id=SOURCE_GROUP_ID,
|
|
)
|
|
|
|
nodes = list(nodes_result)
|
|
print(f' Found {len(nodes)} nodes to migrate')
|
|
|
|
if len(nodes) == 0:
|
|
print(' ⚠️ No nodes found. Nothing to migrate.')
|
|
return
|
|
|
|
# Get all relationships between nodes with the source group_id
|
|
rels_result = session.run(
|
|
"""
|
|
MATCH (n {group_id: $group_id})-[r]->(m {group_id: $group_id})
|
|
RETURN
|
|
id(startNode(r)) as from_id,
|
|
id(endNode(r)) as to_id,
|
|
type(r) as rel_type,
|
|
properties(r) as props
|
|
""",
|
|
group_id=SOURCE_GROUP_ID,
|
|
)
|
|
|
|
relationships = list(rels_result)
|
|
print(f' Found {len(relationships)} relationships to migrate')
|
|
|
|
# Step 2: Create ID mapping (old Neo4j internal ID -> new node UUID)
|
|
print(f'\n📥 Importing data to {TARGET_DATABASE} database (group_id: {TARGET_GROUP_ID})...')
|
|
|
|
id_mapping = {}
|
|
|
|
with driver.session(database=TARGET_DATABASE) as session:
|
|
# Create nodes
|
|
for node in nodes:
|
|
old_id = node['old_id']
|
|
labels = node['labels']
|
|
props = dict(node['props'])
|
|
|
|
# Update group_id
|
|
props['group_id'] = TARGET_GROUP_ID
|
|
|
|
# Get the uuid if it exists (for tracking)
|
|
node_uuid = props.get('uuid', old_id)
|
|
|
|
# Build labels string
|
|
labels_str = ':'.join(labels)
|
|
|
|
# Create node
|
|
result = session.run(
|
|
f"""
|
|
CREATE (n:{labels_str})
|
|
SET n = $props
|
|
RETURN id(n) as new_id, n.uuid as uuid
|
|
""",
|
|
props=props,
|
|
)
|
|
|
|
record = result.single()
|
|
id_mapping[old_id] = record['new_id']
|
|
|
|
print(f' ✅ Created {len(nodes)} nodes')
|
|
|
|
# Create relationships
|
|
rel_count = 0
|
|
for rel in relationships:
|
|
from_old_id = rel['from_id']
|
|
to_old_id = rel['to_id']
|
|
rel_type = rel['rel_type']
|
|
props = dict(rel['props']) if rel['props'] else {}
|
|
|
|
# Update group_id in relationship properties if it exists
|
|
if 'group_id' in props:
|
|
props['group_id'] = TARGET_GROUP_ID
|
|
|
|
# Get new node IDs
|
|
from_new_id = id_mapping.get(from_old_id)
|
|
to_new_id = id_mapping.get(to_old_id)
|
|
|
|
if from_new_id is None or to_new_id is None:
|
|
print(f' ⚠️ Skipping relationship: node mapping not found')
|
|
continue
|
|
|
|
# Create relationship
|
|
session.run(
|
|
f"""
|
|
MATCH (a), (b)
|
|
WHERE id(a) = $from_id AND id(b) = $to_id
|
|
CREATE (a)-[r:{rel_type}]->(b)
|
|
SET r = $props
|
|
""",
|
|
from_id=from_new_id,
|
|
to_id=to_new_id,
|
|
props=props,
|
|
)
|
|
|
|
rel_count += 1
|
|
|
|
print(f' ✅ Created {rel_count} relationships')
|
|
|
|
# Step 3: Verify migration
|
|
print(f'\n✅ Migration complete!')
|
|
print(f'\n📊 Verification:')
|
|
|
|
with driver.session(database=TARGET_DATABASE) as session:
|
|
# Count nodes in target
|
|
result = session.run(
|
|
"""
|
|
MATCH (n {group_id: $group_id})
|
|
RETURN count(n) as node_count
|
|
""",
|
|
group_id=TARGET_GROUP_ID,
|
|
)
|
|
|
|
target_count = result.single()['node_count']
|
|
print(
|
|
f' Target database now has {target_count} nodes with group_id={TARGET_GROUP_ID}'
|
|
)
|
|
|
|
# Show node types
|
|
result = session.run(
|
|
"""
|
|
MATCH (n {group_id: $group_id})
|
|
RETURN labels(n) as labels, count(*) as count
|
|
ORDER BY count DESC
|
|
""",
|
|
group_id=TARGET_GROUP_ID,
|
|
)
|
|
|
|
print(f'\n Node types:')
|
|
for record in result:
|
|
labels = ':'.join(record['labels'])
|
|
count = record['count']
|
|
print(f' {labels}: {count}')
|
|
|
|
print(f'\n🎉 Done! Your data has been migrated successfully.')
|
|
print(f'\nNext steps:')
|
|
print(f'1. Verify the data in Neo4j Browser:')
|
|
print(f' :use graphiti')
|
|
print(f" MATCH (n {{group_id: '{TARGET_GROUP_ID}'}}) RETURN n LIMIT 25")
|
|
print(f'2. Test in LibreChat to ensure everything works')
|
|
print(f'3. Once verified, you can delete the old data:')
|
|
print(f' :use neo4j')
|
|
print(f" MATCH (n {{group_id: '{SOURCE_GROUP_ID}'}}) DETACH DELETE n")
|
|
|
|
finally:
|
|
driver.close()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
print('=' * 70)
|
|
print('Graphiti Data Migration Script')
|
|
print('=' * 70)
|
|
print(f"\nSource: {SOURCE_DATABASE} database, group_id='{SOURCE_GROUP_ID}'")
|
|
print(f"Target: {TARGET_DATABASE} database, group_id='{TARGET_GROUP_ID}'")
|
|
print(f'\nNeo4j URI: {NEO4J_URI}')
|
|
print('=' * 70)
|
|
|
|
response = input("\n⚠️ Ready to migrate? This will copy all data. Type 'yes' to continue: ")
|
|
|
|
if response.lower() == 'yes':
|
|
migrate_data()
|
|
else:
|
|
print('\n❌ Migration cancelled.')
|