We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/doobidoo/mcp-memory-service'
If you have feedback or need assistance with the MCP directory API, please join our Discord server
"""Tests for asymmetric relationship semantic correctness."""
import sys
from pathlib import Path
import pytest
import tempfile
import shutil
import os
import sqlite3
import importlib.util
# Load GraphStorage module directly without importing the package to avoid numpy issues
repo_root = Path(__file__).parent.parent.parent
graph_path = repo_root / "src" / "mcp_memory_service" / "storage" / "graph.py"
ontology_path = repo_root / "src" / "mcp_memory_service" / "models" / "ontology.py"
# Load ontology module first (dependency of graph)
spec = importlib.util.spec_from_file_location("ontology", ontology_path)
ontology_module = importlib.util.module_from_spec(spec)
sys.modules['mcp_memory_service.models.ontology'] = ontology_module
spec.loader.exec_module(ontology_module)
# Load graph module
spec = importlib.util.spec_from_file_location("graph", graph_path)
graph_module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(graph_module)
GraphStorage = graph_module.GraphStorage
@pytest.fixture
def temp_graph_db():
"""Create a temporary database with graph table for testing."""
# Create temporary directory
temp_dir = tempfile.mkdtemp()
db_path = os.path.join(temp_dir, "test_graph.db")
# Initialize database with graph schema
conn = sqlite3.connect(db_path)
conn.execute("""
CREATE TABLE IF NOT EXISTS memory_graph (
source_hash TEXT NOT NULL,
target_hash TEXT NOT NULL,
similarity REAL NOT NULL,
connection_types TEXT NOT NULL,
metadata TEXT,
created_at REAL NOT NULL,
relationship_type TEXT DEFAULT 'related',
PRIMARY KEY (source_hash, target_hash)
)
""")
# Create indexes for performance
conn.execute("""
CREATE INDEX IF NOT EXISTS idx_graph_source
ON memory_graph(source_hash)
""")
conn.execute("""
CREATE INDEX IF NOT EXISTS idx_graph_target
ON memory_graph(target_hash)
""")
conn.execute("""
CREATE INDEX IF NOT EXISTS idx_graph_relationship
ON memory_graph(relationship_type)
""")
conn.commit()
conn.close()
yield db_path
# Cleanup
shutil.rmtree(temp_dir, ignore_errors=True)
@pytest.fixture
async def graph_storage(temp_graph_db):
"""Create GraphStorage instance with initialized database."""
storage = GraphStorage(temp_graph_db)
# Ensure connection is initialized
await storage._get_connection()
yield storage
class TestAsymmetricSemantics:
"""Verify semantic correctness of directional relationships."""
@pytest.mark.asyncio
async def test_causes_directionality(self, graph_storage):
"""A causes B should NOT imply B causes A."""
await graph_storage.store_association(
"decision_a", "error_b", 0.9, ["causal"],
relationship_type="causes"
)
# Forward direction exists
causes = await graph_storage.find_connected(
"decision_a", relationship_type="causes", direction="outgoing"
)
assert "error_b" in [h for h, _ in causes], "decision_a should cause error_b"
# Reverse direction does NOT exist
reverse = await graph_storage.find_connected(
"error_b", relationship_type="causes", direction="outgoing"
)
assert "decision_a" not in [h for h, _ in reverse], "error_b should NOT cause decision_a"
@pytest.mark.asyncio
async def test_fixes_directionality(self, graph_storage):
"""A fixes B should NOT imply B fixes A."""
await graph_storage.store_association(
"decision_fix", "error_bug", 0.95, ["remediation"],
relationship_type="fixes"
)
# Forward direction exists
fixes = await graph_storage.find_connected(
"decision_fix", relationship_type="fixes", direction="outgoing"
)
assert "error_bug" in [h for h, _ in fixes], "decision_fix should fix error_bug"
# Reverse does NOT exist
reverse = await graph_storage.find_connected(
"error_bug", relationship_type="fixes", direction="outgoing"
)
assert "decision_fix" not in [h for h, _ in reverse], "error_bug should NOT fix decision_fix"
@pytest.mark.asyncio
async def test_supports_directionality(self, graph_storage):
"""A supports B should NOT imply B supports A."""
await graph_storage.store_association(
"learning_a", "decision_b", 0.85, ["reinforcement"],
relationship_type="supports"
)
# Forward direction exists
supports = await graph_storage.find_connected(
"learning_a", relationship_type="supports", direction="outgoing"
)
assert "decision_b" in [h for h, _ in supports], "learning_a should support decision_b"
# Reverse does NOT exist
reverse = await graph_storage.find_connected(
"decision_b", relationship_type="supports", direction="outgoing"
)
assert "learning_a" not in [h for h, _ in reverse], "decision_b should NOT support learning_a"
@pytest.mark.asyncio
async def test_follows_directionality(self, graph_storage):
"""A follows B should NOT imply B follows A."""
await graph_storage.store_association(
"observation_a", "observation_b", 0.80, ["temporal"],
relationship_type="follows"
)
# Forward direction exists
follows = await graph_storage.find_connected(
"observation_a", relationship_type="follows", direction="outgoing"
)
assert "observation_b" in [h for h, _ in follows], "observation_a should follow observation_b"
# Reverse does NOT exist
reverse = await graph_storage.find_connected(
"observation_b", relationship_type="follows", direction="outgoing"
)
assert "observation_a" not in [h for h, _ in reverse], "observation_b should NOT follow observation_a"
@pytest.mark.asyncio
async def test_contradicts_bidirectional(self, graph_storage):
"""A contradicts B SHOULD imply B contradicts A (symmetric)."""
await graph_storage.store_association(
"learning_a", "learning_b", 0.85, ["semantic"],
relationship_type="contradicts"
)
# Both directions should work
from_a = await graph_storage.find_connected(
"learning_a", relationship_type="contradicts"
)
from_b = await graph_storage.find_connected(
"learning_b", relationship_type="contradicts"
)
assert "learning_b" in [h for h, _ in from_a], "learning_a should contradict learning_b"
assert "learning_a" in [h for h, _ in from_b], "learning_b should contradict learning_a"
@pytest.mark.asyncio
async def test_related_bidirectional(self, graph_storage):
"""A related B SHOULD imply B related A (symmetric)."""
await graph_storage.store_association(
"observation_a", "observation_b", 0.75, ["semantic"],
relationship_type="related"
)
# Both directions should work
from_a = await graph_storage.find_connected(
"observation_a", relationship_type="related"
)
from_b = await graph_storage.find_connected(
"observation_b", relationship_type="related"
)
assert "observation_b" in [h for h, _ in from_a], "observation_a should be related to observation_b"
assert "observation_a" in [h for h, _ in from_b], "observation_b should be related to observation_a"
@pytest.mark.asyncio
async def test_incoming_query_for_asymmetric(self, graph_storage):
"""Incoming queries should work for asymmetric relationships."""
await graph_storage.store_association(
"decision_a", "error_b", 0.90, ["causal"],
relationship_type="causes"
)
# Can query incoming to find the cause
incoming = await graph_storage.find_connected(
"error_b", relationship_type="causes", direction="incoming"
)
assert "decision_a" in [h for h, _ in incoming], "Should find decision_a as incoming cause"
# Reverse incoming query should be empty
reverse_incoming = await graph_storage.find_connected(
"decision_a", relationship_type="causes", direction="incoming"
)
assert "error_b" not in [h for h, _ in reverse_incoming], "error_b should not be an incoming cause"
@pytest.mark.asyncio
async def test_direction_both_with_asymmetric(self, graph_storage):
"""Direction='both' should work correctly with asymmetric relationships."""
await graph_storage.store_association(
"decision_a", "error_b", 0.90, ["causal"],
relationship_type="causes"
)
# Query from decision_a with direction="both"
from_a = await graph_storage.find_connected(
"decision_a", relationship_type="causes", direction="both"
)
assert "error_b" in [h for h, _ in from_a], "Should find error_b from decision_a"
# Query from error_b with direction="both"
from_b = await graph_storage.find_connected(
"error_b", relationship_type="causes", direction="both"
)
assert "decision_a" in [h for h, _ in from_b], "Should find decision_a from error_b"