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
160 lines
4.6 KiB
Python
160 lines
4.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Verify migration data in Neo4j."""
|
|
|
|
import json
|
|
import os
|
|
|
|
from neo4j import GraphDatabase
|
|
|
|
NEO4J_URI = 'bolt://192.168.1.25:7687'
|
|
NEO4J_USER = 'neo4j'
|
|
NEO4J_PASSWORD = '!"MiTa1205'
|
|
|
|
TARGET_DATABASE = 'graphiti'
|
|
TARGET_GROUP_ID = '6910959f2128b5c4faa22283'
|
|
|
|
driver = GraphDatabase.driver(NEO4J_URI, auth=(NEO4J_USER, NEO4J_PASSWORD))
|
|
|
|
print('=' * 70)
|
|
print('Verifying Migration Data')
|
|
print('=' * 70)
|
|
|
|
with driver.session(database=TARGET_DATABASE) as session:
|
|
# Check total nodes
|
|
result = session.run(
|
|
"""
|
|
MATCH (n {group_id: $group_id})
|
|
RETURN count(n) as total
|
|
""",
|
|
group_id=TARGET_GROUP_ID,
|
|
)
|
|
|
|
total = result.single()['total']
|
|
print(f"\n✓ Total nodes with group_id '{TARGET_GROUP_ID}': {total}")
|
|
|
|
# Check node labels and properties
|
|
result = session.run(
|
|
"""
|
|
MATCH (n {group_id: $group_id})
|
|
RETURN DISTINCT 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}')
|
|
|
|
# Sample some episodic nodes
|
|
result = session.run(
|
|
"""
|
|
MATCH (n:Episodic {group_id: $group_id})
|
|
RETURN n.uuid as uuid, n.name as name, n.content as content, n.created_at as created_at
|
|
LIMIT 5
|
|
""",
|
|
group_id=TARGET_GROUP_ID,
|
|
)
|
|
|
|
print(f'\n✓ Sample Episodic nodes:')
|
|
episodes = list(result)
|
|
if episodes:
|
|
for record in episodes:
|
|
print(f' - {record["name"]}')
|
|
print(f' UUID: {record["uuid"]}')
|
|
print(f' Created: {record["created_at"]}')
|
|
print(f' Content: {record["content"][:100] if record["content"] else "None"}...')
|
|
else:
|
|
print(' ⚠️ No episodic nodes found!')
|
|
|
|
# Sample some entity nodes
|
|
result = session.run(
|
|
"""
|
|
MATCH (n:Entity {group_id: $group_id})
|
|
RETURN n.uuid as uuid, n.name as name, labels(n) as labels, n.summary as summary
|
|
LIMIT 10
|
|
""",
|
|
group_id=TARGET_GROUP_ID,
|
|
)
|
|
|
|
print(f'\n✓ Sample Entity nodes:')
|
|
entities = list(result)
|
|
if entities:
|
|
for record in entities:
|
|
labels = ':'.join(record['labels'])
|
|
print(f' - {record["name"]} ({labels})')
|
|
print(f' UUID: {record["uuid"]}')
|
|
if record['summary']:
|
|
print(f' Summary: {record["summary"][:80]}...')
|
|
else:
|
|
print(' ⚠️ No entity nodes found!')
|
|
|
|
# Check relationships
|
|
result = session.run(
|
|
"""
|
|
MATCH (n {group_id: $group_id})-[r]->(m {group_id: $group_id})
|
|
RETURN type(r) as rel_type, count(*) as count
|
|
ORDER BY count DESC
|
|
LIMIT 10
|
|
""",
|
|
group_id=TARGET_GROUP_ID,
|
|
)
|
|
|
|
print(f'\n✓ Relationship types:')
|
|
rels = list(result)
|
|
if rels:
|
|
for record in rels:
|
|
print(f' {record["rel_type"]}: {record["count"]}')
|
|
else:
|
|
print(' ⚠️ No relationships found!')
|
|
|
|
# Check if nodes have required properties
|
|
result = session.run(
|
|
"""
|
|
MATCH (n:Episodic {group_id: $group_id})
|
|
RETURN
|
|
count(n) as total,
|
|
count(n.uuid) as has_uuid,
|
|
count(n.name) as has_name,
|
|
count(n.content) as has_content,
|
|
count(n.created_at) as has_created_at,
|
|
count(n.valid_at) as has_valid_at
|
|
""",
|
|
group_id=TARGET_GROUP_ID,
|
|
)
|
|
|
|
props = result.single()
|
|
print(f'\n✓ Episodic node properties:')
|
|
print(f' Total: {props["total"]}')
|
|
print(f' Has uuid: {props["has_uuid"]}')
|
|
print(f' Has name: {props["has_name"]}')
|
|
print(f' Has content: {props["has_content"]}')
|
|
print(f' Has created_at: {props["has_created_at"]}')
|
|
print(f' Has valid_at: {props["has_valid_at"]}')
|
|
|
|
# Check Entity properties
|
|
result = session.run(
|
|
"""
|
|
MATCH (n:Entity {group_id: $group_id})
|
|
RETURN
|
|
count(n) as total,
|
|
count(n.uuid) as has_uuid,
|
|
count(n.name) as has_name,
|
|
count(n.summary) as has_summary,
|
|
count(n.created_at) as has_created_at
|
|
""",
|
|
group_id=TARGET_GROUP_ID,
|
|
)
|
|
|
|
props = result.single()
|
|
print(f'\n✓ Entity node properties:')
|
|
print(f' Total: {props["total"]}')
|
|
print(f' Has uuid: {props["has_uuid"]}')
|
|
print(f' Has name: {props["has_name"]}')
|
|
print(f' Has summary: {props["has_summary"]}')
|
|
print(f' Has created_at: {props["has_created_at"]}')
|
|
|
|
driver.close()
|
|
print('\n' + '=' * 70)
|