Skip to main content
Glama
convert_to_memory_mcp.py13.7 kB
#!/usr/bin/env python3 """Convert cortexgraph JSONL format to Anthropic Memory MCP format. This script converts cortexgraph memories and relations to the memory.json format used by Anthropic's Memory MCP server. Conversion Logic: ----------------- 1. Each cortexgraph memory becomes a Memory MCP entity: - name: memory.id (unique identifier) - entityType: "memory" - observations: [content, tags as formatted strings, entities as formatted strings] 2. Each cortexgraph relation becomes a Memory MCP relation: - from: relation.from_memory_id - to: relation.to_memory_id - relationType: relation.relation_type Input Format (cortexgraph): --------------------- memories.jsonl: { "id": "mem-123", "content": "User prefers TypeScript over JavaScript", "meta": {"tags": ["preferences", "typescript"], "source": "conversation", ...}, "entities": ["TypeScript", "JavaScript"], "created_at": 1234567890, "last_used": 1234567890, "use_count": 5, "strength": 1.2, ... } relations.jsonl: { "id": "rel-456", "from_memory_id": "mem-123", "to_memory_id": "mem-789", "relation_type": "related", "strength": 0.8, ... } Output Format (Memory MCP): -------------------------- memory.json: { "entities": [ { "name": "mem-123", "entityType": "memory", "observations": [ "Content: User prefers TypeScript over JavaScript", "Tags: preferences, typescript", "Entities: TypeScript, JavaScript", "Created: 2009-02-13 23:31:30", "Use count: 5", "Strength: 1.2" ] } ], "relations": [ { "from": "mem-123", "to": "mem-789", "relationType": "related" } ] } Usage: ------ # Basic usage with default paths python convert_to_memory_mcp.py # Specify custom input/output paths python convert_to_memory_mcp.py \\ --memories-input ~/.config/cortexgraph/jsonl/memories.jsonl \\ --relations-input ~/.config/cortexgraph/jsonl/relations.jsonl \\ --output memory.json # Dry run to preview conversion python convert_to_memory_mcp.py --dry-run # Verbose output python convert_to_memory_mcp.py --verbose Examples: --------- # Convert default cortexgraph storage to memory.json $ python convert_to_memory_mcp.py Converted 42 memories and 17 relations to memory.json # Preview conversion without writing file $ python convert_to_memory_mcp.py --dry-run --verbose Would convert 42 memories: - mem-123: User prefers TypeScript... - mem-456: API authentication uses JWT... Would convert 17 relations: - mem-123 -> mem-789 (related) - mem-456 -> mem-123 (references) Requirements: ------------ - Python 3.10+ - No external dependencies (uses stdlib only) """ import argparse import json import sys from datetime import datetime from pathlib import Path from typing import Any def convert_memory_to_entity(memory: dict[str, Any]) -> dict[str, Any]: """Convert a cortexgraph memory to a Memory MCP entity. Args: memory: Dictionary containing cortexgraph memory data Returns: Dictionary in Memory MCP entity format with name, entityType, and observations """ # Extract metadata meta = memory.get("meta", {}) if isinstance(meta, str): meta = json.loads(meta) tags = meta.get("tags", []) entities = memory.get("entities", []) created_at = memory.get("created_at") use_count = memory.get("use_count", 0) strength = memory.get("strength", 1.0) source = meta.get("source") context = meta.get("context") # Build observations list observations = [] # Primary content content = memory.get("content", "") if content: observations.append(f"Content: {content}") # Tags if tags: observations.append(f"Tags: {', '.join(tags)}") # Entities if entities: observations.append(f"Entities: {', '.join(entities)}") # Created timestamp (human-readable) if created_at: try: dt = datetime.fromtimestamp(created_at) observations.append(f"Created: {dt.strftime('%Y-%m-%d %H:%M:%S')}") except (ValueError, OSError): observations.append(f"Created: {created_at}") # Use count if use_count > 0: observations.append(f"Use count: {use_count}") # Strength (if not default) if strength != 1.0: observations.append(f"Strength: {strength:.2f}") # Source if source: observations.append(f"Source: {source}") # Context if context: observations.append(f"Context: {context}") # Return entity return { "name": memory["id"], "entityType": "memory", "observations": observations, } def convert_relation(relation: dict[str, Any]) -> dict[str, Any]: """Convert a cortexgraph relation to a Memory MCP relation. Args: relation: Dictionary containing cortexgraph relation data Returns: Dictionary in Memory MCP relation format with from, to, and relationType """ return { "from": relation["from_memory_id"], "to": relation["to_memory_id"], "relationType": relation["relation_type"], } def load_jsonl(file_path: Path) -> list[dict[str, Any]]: """Load JSONL file into list of dictionaries. Args: file_path: Path to JSONL file Returns: List of dictionaries parsed from JSONL Raises: FileNotFoundError: If file doesn't exist json.JSONDecodeError: If file contains invalid JSON """ if not file_path.exists(): raise FileNotFoundError(f"Input file not found: {file_path}") records = [] with open(file_path, encoding="utf-8") as f: for line_num, line in enumerate(f, start=1): line = line.strip() if not line: continue try: records.append(json.loads(line)) except json.JSONDecodeError as e: print( f"Warning: Skipping invalid JSON on line {line_num}: {e}", file=sys.stderr, ) return records def validate_output(output: dict[str, Any]) -> list[str]: """Validate the output format matches Memory MCP schema. Args: output: Dictionary to validate Returns: List of validation error messages (empty if valid) """ errors = [] # Check top-level structure if "entities" not in output: errors.append("Missing required field: entities") if "relations" not in output: errors.append("Missing required field: relations") # Validate entities if "entities" in output: if not isinstance(output["entities"], list): errors.append("entities must be a list") else: for i, entity in enumerate(output["entities"]): if "name" not in entity: errors.append(f"Entity {i}: missing required field 'name'") if "entityType" not in entity: errors.append(f"Entity {i}: missing required field 'entityType'") if "observations" not in entity: errors.append(f"Entity {i}: missing required field 'observations'") elif not isinstance(entity["observations"], list): errors.append(f"Entity {i}: observations must be a list") # Validate relations if "relations" in output: if not isinstance(output["relations"], list): errors.append("relations must be a list") else: for i, relation in enumerate(output["relations"]): if "from" not in relation: errors.append(f"Relation {i}: missing required field 'from'") if "to" not in relation: errors.append(f"Relation {i}: missing required field 'to'") if "relationType" not in relation: errors.append(f"Relation {i}: missing required field 'relationType'") return errors def convert( memories_path: Path, relations_path: Path, output_path: Path, *, dry_run: bool = False, verbose: bool = False, ) -> dict[str, Any]: """Convert cortexgraph JSONL files to Memory MCP format. Args: memories_path: Path to memories.jsonl relations_path: Path to relations.jsonl output_path: Path to output memory.json dry_run: If True, don't write output file verbose: If True, print detailed conversion info Returns: Dictionary containing converted data Raises: FileNotFoundError: If input files don't exist ValueError: If conversion produces invalid output """ # Load input files if verbose: print(f"Loading memories from {memories_path}") memories = load_jsonl(memories_path) if verbose: print(f"Loading relations from {relations_path}") relations = load_jsonl(relations_path) # Convert if verbose: print(f"Converting {len(memories)} memories to entities...") entities = [convert_memory_to_entity(m) for m in memories] if verbose: print(f"Converting {len(relations)} relations...") converted_relations = [convert_relation(r) for r in relations] # Build output output = {"entities": entities, "relations": converted_relations} # Validate validation_errors = validate_output(output) if validation_errors: raise ValueError( "Output validation failed:\n" + "\n".join(f" - {e}" for e in validation_errors) ) # Write output (unless dry run) if dry_run: if verbose: print("\n[DRY RUN] Would write to:", output_path) print(f"Entities: {len(entities)}") print(f"Relations: {len(converted_relations)}") if entities: print("\nSample entity:") print(json.dumps(entities[0], indent=2)) if converted_relations: print("\nSample relation:") print(json.dumps(converted_relations[0], indent=2)) else: if verbose: print(f"Writing to {output_path}...") output_path.parent.mkdir(parents=True, exist_ok=True) with open(output_path, "w", encoding="utf-8") as f: json.dump(output, f, indent=2, ensure_ascii=False) if verbose: print( f"Successfully wrote {len(entities)} entities and {len(converted_relations)} relations" ) return output def get_default_paths() -> tuple[Path, Path]: """Get default paths for cortexgraph storage. Returns: Tuple of (memories_path, relations_path) """ # Try to read from environment or use default import os storage_path = os.getenv("CORTEXGRAPH_STORAGE_PATH", "~/.config/cortexgraph/jsonl") storage_path = Path(storage_path).expanduser() return ( storage_path / "memories.jsonl", storage_path / "relations.jsonl", ) def main(): """CLI entry point.""" parser = argparse.ArgumentParser( description="Convert cortexgraph JSONL format to Anthropic Memory MCP format", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: %(prog)s %(prog)s --dry-run --verbose %(prog)s --memories-input data/memories.jsonl --output memory.json """, ) # Input arguments memories_default, relations_default = get_default_paths() parser.add_argument( "--memories-input", type=Path, default=memories_default, help=f"Path to memories.jsonl (default: {memories_default})", ) parser.add_argument( "--relations-input", type=Path, default=relations_default, help=f"Path to relations.jsonl (default: {relations_default})", ) parser.add_argument( "--output", "-o", type=Path, default=Path("memory.json"), help="Path to output memory.json (default: memory.json)", ) # Options parser.add_argument( "--dry-run", action="store_true", help="Preview conversion without writing output file", ) parser.add_argument( "--verbose", "-v", action="store_true", help="Print detailed conversion information", ) args = parser.parse_args() # Convert try: output = convert( memories_path=args.memories_input, relations_path=args.relations_input, output_path=args.output, dry_run=args.dry_run, verbose=args.verbose, ) # Print summary if not args.verbose: if args.dry_run: print( f"[DRY RUN] Would convert {len(output['entities'])} memories " f"and {len(output['relations'])} relations to {args.output}" ) else: print( f"Converted {len(output['entities'])} memories " f"and {len(output['relations'])} relations to {args.output}" ) sys.exit(0) except FileNotFoundError as e: print(f"Error: {e}", file=sys.stderr) sys.exit(1) except ValueError as e: print(f"Validation error: {e}", file=sys.stderr) sys.exit(1) except Exception as e: print(f"Unexpected error: {e}", file=sys.stderr) if args.verbose: import traceback traceback.print_exc() sys.exit(1) if __name__ == "__main__": main()

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/prefrontalsys/mnemex'

If you have feedback or need assistance with the MCP directory API, please join our Discord server