Skip to main content
Glama
jewelstrike.py29 kB
#!/usr/bin/env python3 """ JEWELSTRIKE - Strike the phantom gem to create working_gem + witness_gem Part of the JEWELSTORM workflow (parallel to QUILLSTORM for PHANTOM GEMs). Purpose: - Creates fresh working_gem (UUID-prefixed temp copy for semantic editing) - Creates witness_gem (point-in-time backup for comparison) - Adds jewel_id to all nodes (local handles for transform_jewel operations) - GUARANTEES fresh read (working_gem created at invocation time) - Enables parallel conversation safety (unique UUID prevents collision) Usage: python jewelstrike.py "<nexus-tag>" [human-prefix] Returns JSON to stdout: { "working_gem": "path/to/temp/jm-[hash]-[prefix]--phantom_gem.json", "witness_gem": "path/to/temp/jm-[hash]-[prefix]--phantom_gem.json.original.json", "hash": "a3f7", "phantom_gem": "path/to/original/phantom_gem.json", "guardian_token": "hidden-from-agent" } """ import os import sys import json import shutil import secrets from pathlib import Path INDEX_FILENAME = "jewelstorm_index.json" def build_jewel_map(nodes: list, depth: int = 0) -> dict: """Build indented jewel_map showing tree structure. Returns dict mapping jewel_id to indented name string. Uses bullets (•) and 4-space indentation per level. """ jewel_map = {} indent = " " * depth bullet = "• " if depth == 0 else "• " for node in nodes: jewel_id = node.get("jewel_id") name = node.get("name", "Untitled") # Truncate very long names if len(name) > 80: name = name[:77] + "..." jewel_map[jewel_id] = f"{indent}{bullet}{name}" # Recurse into children children = node.get("children", []) if children: child_map = build_jewel_map(children, depth + 1) jewel_map.update(child_map) return jewel_map def _build_jewel_preview_lines( roots: list, max_note_chars: int = 1024, weave_would_delete_ids: set[str] | None = None, weave_would_delete_emoji: str = "🧨", local_only_delete_ids: set[str] | None = None, local_only_delete_emoji: str = "🪵", ) -> list[str]: """Build human-readable preview of JEWEL tree (JEWELSTRIKE initial state). Format: [J-001] ⦿ Node Name [note-preview] with 4-space indentation per depth level and truncated note previews. This helper is shared with nexus_json_tools.transform_jewel so both JEWELSTRIKE and transform_jewel produce consistent JEWEL previews. """ # PASS 1: Collect all jewel_id / id labels to compute max width all_labels: list[str] = [] def collect_labels(node: dict) -> None: jewel_id = node.get("jewel_id") or node.get("id") or "?" all_labels.append(str(jewel_id)) children = node.get("children") or [] for child in children: if isinstance(child, dict): collect_labels(child) for root in roots or []: if isinstance(root, dict): collect_labels(root) max_id_width = max((len(lbl) for lbl in all_labels), default=0) # PASS 2: Build aligned preview lines lines: list[str] = [] def walk(node: dict, depth: int) -> None: jewel_id = node.get("jewel_id") or node.get("id") or "?" id_label = str(jewel_id).ljust(max_id_width) indent = " " * 4 * depth children = node.get("children") or [] has_child_dicts = any(isinstance(c, dict) for c in children) bullet = "•" if not has_child_dicts else "⦿" name = node.get("name") or "Untitled" note = node.get("note") or "" # Surface SKELETON role hints in the preview (delete vs merge targets) sk_hint = node.get("skeleton_hint") if not sk_hint and node.get("subtree_mode") == "shell": sk_hint = "DELETE_SECTION" hint_parts = [] # Best-effort: mark nodes by "what deletion would actually do" in WEAVE. # # 🧨 = WEAVE-WOULD-DELETE (if you delete this node in JEWEL, it can delete in ETHER) # 🪵 = LOCAL-ONLY (don't be fooled: deleting this node in JEWEL won't delete in ETHER) try: wf_id = node.get("id") if wf_id: wf_id_str = str(wf_id) if weave_would_delete_ids is not None and wf_id_str in weave_would_delete_ids: hint_parts.append(f"{weave_would_delete_emoji}[WEAVE-WOULD-DELETE]") elif local_only_delete_ids is not None and wf_id_str in local_only_delete_ids: hint_parts.append(f"{local_only_delete_emoji}[LOCAL-ONLY]") except Exception: pass if sk_hint == "DELETE_SECTION": hint_parts.append("[DELETE]") elif sk_hint == "MERGE_TARGET": hint_parts.append("[MERGE]") hint_prefix = (" ".join(hint_parts) + " ") if hint_parts else "" if isinstance(note, str) and note: flat = note.replace("\n", "\\n") if len(flat) > max_note_chars: flat = flat[:max_note_chars] name_part = f"{hint_prefix}{name} [{flat}]" else: name_part = f"{hint_prefix}{name}" lines.append(f"[{id_label}] {indent}{bullet} {name_part}") for child in children: if isinstance(child, dict): walk(child, depth + 1) for root in roots or []: if isinstance(root, dict): walk(root, 0) return lines def add_jewel_ids_recursive(node: dict, counter: list) -> None: """Add jewel_id to node and all descendants recursively, stripping noisy fields. This prepares the HOT VISIBLE GEM for JEWELSTORM by removing per-node metadata that is not needed for semantic editing (and inflates token usage): - completed - data (e.g., {"layoutMode": "bullets"}) - createdAt / modifiedAt / completedAt The full metadata remains available in phantom_gem.json and, if desired, in witness/guardian copies. The working_gem focuses on structure and content (id, jewel_id, name, note, children). """ if "jewel_id" not in node: node["jewel_id"] = f"J-{counter[0]:03d}" counter[0] += 1 # Strip per-node noise fields for HOT VISIBLE GEM for key in ("completed", "data", "createdAt", "modifiedAt", "completedAt"): if key in node: node.pop(key, None) children = node.get("children") or [] for child in children: add_jewel_ids_recursive(child, counter) def jewelstrike(phantom_gem_file: str, human_prefix: str | None = None) -> dict: """ JEWELSTRIKE the phantom gem - it sunders into working_gem + witness_gem. Creates: - working_gem: UUID-prefixed temp copy with jewel_ids added - witness_gem: Point-in-time backup for comparison Args: phantom_gem_file: Full path to phantom_gem.json to strike human_prefix: Optional human-friendly string for temp filenames Returns: Dictionary with paths to working_gem, witness_gem, hash, and guardian token """ # Validate phantom_gem exists gem_path = Path(phantom_gem_file).resolve() if not gem_path.exists(): return { "success": False, "error": f"Phantom gem file does not exist: {phantom_gem_file}" } # Temp directory (same as QUILLSTRIKE) temp_dir = Path(r"E:\__daniel347x\__Obsidian\__Inking into Mind\--TypingMind\Projects - All\Projects - Individual\TODO\temp") temp_dir.mkdir(exist_ok=True) # Guardian directory guardian_dir = temp_dir / ".jewel-guardian" guardian_dir.mkdir(exist_ok=True) # Load or initialize index index_path = guardian_dir / INDEX_FILENAME try: if index_path.exists(): with open(index_path, "r", encoding="utf-8") as f: index = json.load(f) else: index = {} except Exception: index = {} gem_key = str(gem_path) existing = index.get(gem_key) if existing is not None: return { "success": False, "error": ( "💎 THE JEWEL GUARDIAN IS ALREADY ACTIVE. A prismatic field currently protects this phantom gem.\n\n" f"File: {gem_key}\n" f"Active hash: {existing.get('hash')}\n\n" "You cannot strike a new field while one is already shimmering.\n" "Use jeweldrop.py to collapse the existing session if it is stale." ), } # Generate 4-character hash for filename prefix hash_value = secrets.token_hex(2) # Generate 8-character guardian token guardian_token = secrets.token_hex(4) # Determine paths basename = gem_path.name if human_prefix: safe_prefix = "".join(c if c.isalnum() or c in "-_" else "-" for c in human_prefix).strip("-") file_prefix = f"jm-{hash_value}-{safe_prefix}--" else: file_prefix = f"jm-{hash_value}--" # Working gem working_gem = temp_dir / f"{file_prefix}{basename}" # Witness gem witness_gem = temp_dir / f"{file_prefix}{basename}.original.json" # Guardian gem (hidden) guardian_gem = guardian_dir / f"{hash_value}-{safe_prefix if human_prefix else 'gem'}.witness" # Delete stale files if witness_gem.exists(): witness_gem.unlink() if guardian_gem.exists(): guardian_gem.unlink() # Load phantom_gem JSON and add jewel_ids try: with open(gem_path, "r", encoding="utf-8") as f: gem_data = json.load(f) except Exception as e: return { "success": False, "error": f"Failed to read phantom_gem JSON: {e}" } # Write full operations reference to separate file in temp directory operations_reference = { "_HELP": "JEWELSTORM Operations Reference - Complete catalog for nexus_transform_jewel", "_NOTE": "This file is AUTO-GENERATED by jewelstrike.py and AUTO-DELETED by jewelmorph.py. Do not edit manually.", "MOVE_NODE": { "description": "Move a node to a new parent or reorder siblings", "required": ["jewel_id"], "optional": ["new_parent_jewel_id", "position", "relative_to_jewel_id"], "position_values": ["FIRST", "LAST", "BEFORE", "AFTER"], "notes": [ "Must provide either new_parent_jewel_id OR relative_to_jewel_id (for BEFORE/AFTER)", "Position defaults to LAST if not specified", "BEFORE/AFTER require relative_to_jewel_id to specify anchor sibling" ] }, "DELETE_NODE": { "description": "Delete a node and its entire subtree", "required": ["jewel_id"], "optional": ["delete_from_ether", "mode"], "modes": ["SMART (default)", "FAIL_IF_HAS_CHILDREN"], "notes": [ "SMART mode: Allows deleting nodes with JEWEL-only children; requires delete_from_ether=true for ETHER-backed children", "Deleted nodes are removed from working_gem; JEWELMORPH preserves deletion in phantom_jewel.json" ] }, "DELETE_ALL_CHILDREN": { "description": "Delete all immediate children of a node (keep the node itself)", "required": ["jewel_id"], "optional": ["delete_from_ether", "mode"], "notes": [ "Same SMART/FAIL_IF_HAS_CHILDREN semantics as DELETE_NODE", "Use to clear a parent before adding new children" ] }, "RENAME_NODE": { "description": "Change the name (title) of a node", "required": ["jewel_id"], "parameter_options": ["name (recommended)", "new_name (legacy)"], "notes": [ "Both 'name' and 'new_name' are accepted for backward compatibility", "Use 'name' for consistency with CREATE_NODE" ] }, "SET_NOTE": { "description": "Set or update the note field of a node", "required": ["jewel_id"], "parameter_options": ["note (recommended)", "new_note (legacy)"], "notes": [ "Both 'note' and 'new_note' are accepted for backward compatibility", "Use 'note' for consistency with CREATE_NODE", "Pass empty string or null to clear the note" ] }, "SET_ATTRS": { "description": "Set node attributes (completed, layoutMode, priority, tags)", "required": ["jewel_id", "attrs (dict)"], "allowed_attr_keys": ["completed", "layoutMode", "priority", "tags"], "notes": [ "attrs must be a dict mapping allowed keys to values", "Set value to null to remove an attribute", "Example: {'completed': true, 'layoutMode': 'todo'}" ] }, "CREATE_NODE": { "description": "Create a new node with optional children (recursive subtree)", "required": ["name"], "optional": ["parent_jewel_id", "position", "relative_to_jewel_id", "note", "attrs", "data", "jewel_id", "children", "node"], "position_values": ["FIRST", "LAST", "BEFORE", "AFTER"], "notes": [ "Must provide either parent_jewel_id OR relative_to_jewel_id (for BEFORE/AFTER)", "New nodes are created WITHOUT Workflowy id (JEWELMORPH preserves this so WEAVE treats them as CREATE operations)", "Can provide jewel_id explicitly or let system auto-generate", "Supports nested children array for recursive tree creation", "Two formats: compact (name/note/children at op level) or wrapped (inside 'node' key)" ] }, "SEARCH_REPLACE": { "description": "Find and replace text across all nodes in GEM", "required": ["search", "replace", "fields"], "optional": ["case_sensitive", "whole_word", "regex"], "fields_values": ["name", "note", "both"], "notes": [ "Operates on working_gem after JEWELSTRIKE", "Returns nodes_renamed and notes_updated counts", "Use dry_run: true to preview changes" ] }, "SEARCH_AND_TAG": { "description": "Find text and add #tag to matching nodes", "required": ["search", "tag", "fields"], "optional": ["case_sensitive", "whole_word", "regex"], "fields_values": ["name", "note", "both"], "notes": [ "Same search params as SEARCH_REPLACE", "Adds #tag to specified field(s) when match found" ] }, "SET_ATTRS_BY_PATH": { "description": "Set attributes on a node by index path (used internally for JEWEL UUID injection after WEAVE CREATE phase)", "required": ["path (list of ints)", "attrs (dict)"], "allowed_attr_keys": ["id (Workflowy UUID)"], "notes": [ "Path is list of 0-based indexes into nodes array and nested children", "Example: path=[0, 2, 1] navigates to nodes[0].children[2].children[1]", "Currently only supports setting 'id' attribute (for WEAVE Phase 1 UUID sync)" ] }, "WORKFLOW_REMINDER": { "JEWELSTORM_LIFECYCLE": [ "1. JEWELSTRIKE <nexus-tag> → Creates working_gem with jewel_ids", "2. JEWELSTORM editing → Use nexus_transform_jewel MCP tool", "3. JEWELMORPH → Strips jewel_ids, reattaches metadata, produces phantom_jewel.json", "4. nexus_anchor_jewels → 3-way fuse (T1 + S0 + S1 → enchanted_terrain.json)", "5. nexus_weave_enchanted_async → Apply enchanted_terrain back to Workflowy ETHER" ], "IDENTITY_NOTES": [ "jewel_id: Local handles for JEWELSTORM editing (J-001, J-002, etc.)", "id: Workflowy UUID (preserved for existing nodes, omitted for new nodes)", "New nodes without 'id' field → WEAVE creates them in Workflowy", "Existing nodes with 'id' field → WEAVE updates them in place" ] } } # Write full reference to separate file (same directory as working/witness gems) reference_file = working_gem.with_name(f"{file_prefix}operations_reference.json") try: with open(reference_file, "w", encoding="utf-8") as f: json.dump(operations_reference, f, ensure_ascii=False, indent=2) except Exception as e: # Non-fatal - working_gem can still function without external reference pass # Capture deletion-semantics ledger BEFORE we strip metadata from the HOT VISIBLE GEM. # # ⚠️ CRITICAL: delete_semantics_hint relies on original_ids_seen; if we pop it first, # the hint (🧨/🪵 markers + counts) becomes empty even though phantom_gem.json contains it. original_ids_seen_list = [] try: if isinstance(gem_data, dict): original_ids_seen_list = gem_data.get("original_ids_seen") or [] except Exception: original_ids_seen_list = [] # Strip root-level heavy metadata from HOT VISIBLE GEM (phantom_gem still has it) for key in ( "original_ids_seen", "explicitly_preserved_ids", "export_root_id", "export_root_name", "export_root_children_status", "jewel_file", ): if key in gem_data: gem_data.pop(key, None) # Add jewel_ids to all nodes counter = [1] # Mutable counter for recursion nodes = gem_data.get("nodes", []) for node in nodes: add_jewel_ids_recursive(node, counter) # Normalize key order so id / preview_id / jewel_id appear at the top of each node def _normalize_node_key_order(node: dict) -> dict: front_keys = ["id", "preview_id", "jewel_id"] ordered: dict = {} for k in front_keys: if k in node: ordered[k] = node[k] for k, v in node.items(): if k not in ordered: ordered[k] = v children = ordered.get("children") if isinstance(children, list): new_children = [] for ch in children: if isinstance(ch, dict): new_children.append(_normalize_node_key_order(ch)) else: new_children.append(ch) ordered["children"] = new_children return ordered if isinstance(nodes, list): gem_data["nodes"] = [_normalize_node_key_order(n) for n in nodes if isinstance(n, dict)] # Build JEWEL preview (ephemeral, for agents/humans; never read by algorithms) # NOTE: We compute candidate_deletable_ids first so we can annotate preview lines. preview_tree = [] # Usability: surface WEAVE deletion semantics (fail-closed) # # After our WEAVE hardening, a node is eligible for deletion in ETHER only if: # tid ∈ original_ids_seen AND tid ∉ truncated_parents # # Here, we can at least expose ledger presence/count and a coarse list of # "candidate deletable" nodes based on children_status/subtree_mode. weave_would_delete_ids = [] local_only_ids = [] if isinstance(original_ids_seen_list, list) and original_ids_seen_list: # Walk nodes and partition IDs into: # - weave_would_delete_ids: satisfies WEAVE deletion gates (best-effort heuristic) # - local_only_ids: has real id + in original_ids_seen, but is locally truncated/opaque def _is_node_locally_truncated(n: dict) -> bool: # Match WEAVE semantics (Dec 2025): subtree_mode='shell' is editing-scope, # not a deletion-safety gate. status = n.get("children_status") if status is None or status != "complete": return True # has_hidden_children remains a valid local truncation hint # (used by some exporters/previews) if n.get("has_hidden_children") is True: return True return False def _walk_collect(n: dict) -> None: nid = n.get("id") if nid and (str(nid) in original_ids_seen_list): if _is_node_locally_truncated(n): local_only_ids.append(str(nid)) else: weave_would_delete_ids.append(str(nid)) for ch in n.get("children") or []: if isinstance(ch, dict): _walk_collect(ch) for r in nodes or []: if isinstance(r, dict): _walk_collect(r) # Now we can render the preview with per-node WEAVE outcome markers. try: preview_tree = _build_jewel_preview_lines( nodes, weave_would_delete_ids=set(weave_would_delete_ids), weave_would_delete_emoji="🧨", local_only_delete_ids=set(local_only_ids), local_only_delete_emoji="🪵", ) except Exception: preview_tree = [] # Build jewel_map showing tree structure with indentation jewel_map = build_jewel_map(nodes) # Create minimal inline header (replaces bloated operations catalog) minimal_header = { "__preview_tree__": preview_tree, "_jewelstorm_help": { "operations": "CREATE_NODE, DELETE_NODE, MOVE_NODE, RENAME_NODE, SET_NOTE, SET_ATTRS, SEARCH_REPLACE, SEARCH_AND_TAG", "full_docs": str(reference_file), "jewel_map": jewel_map } } # Inject minimal header at root level if this is an export package # Also persist nexus_tag for downstream managed JEWELSTORM tooling. if isinstance(gem_data, dict) and "nodes" in gem_data: gem_data = {**minimal_header, "nexus_tag": nexus_tag, **gem_data} # Write working_gem (with jewel_ids + operations help block) try: with open(working_gem, "w", encoding="utf-8") as f: json.dump(gem_data, f, ensure_ascii=False, indent=2) except Exception as e: return { "success": False, "error": f"Failed to write working_gem: {e}" } # Write witness_gem (with jewel_ids - same as working_gem initially) shutil.copy2(working_gem, witness_gem) # Write guardian_gem (with jewel_ids - HIDDEN validation copy) shutil.copy2(working_gem, guardian_gem) # Update index try: index[gem_key] = { "hash": hash_value, "human_prefix": safe_prefix if human_prefix else None, "working_gem": str(working_gem), "witness_gem": str(witness_gem), "guardian_token": guardian_token, } with open(index_path, "w", encoding="utf-8") as f: json.dump(index, f, indent=2) except Exception as e: return { "success": False, "error": f"Failed to update guardian index: {e}" } return { "success": True, "working_gem": str(working_gem), "witness_gem": str(witness_gem), "hash": hash_value, "phantom_gem": str(gem_path), "preview_tree": preview_tree, "delete_semantics_hint": { "rule": "WEAVE deletes only if (tid ∈ original_ids_seen) AND (tid is NOT truncated)", "weave_markers": { "🧨": "[WEAVE-WOULD-DELETE] (best-effort heuristic)", "🪵": "[LOCAL-ONLY] Don't be fooled: deleting this node in JEWEL will NOT delete it in ETHER (best-effort heuristic)", }, "original_ids_seen_count": len(original_ids_seen_list) if isinstance(original_ids_seen_list, list) else 0, "weave_would_delete_ids_count": len(weave_would_delete_ids), "local_only_ids_count": len(local_only_ids), "note": "If you intend to delete any subtree that could reach ETHER during WEAVE, include confirm_delete_known_descendants_from_ether=True in the DELETE operation." }, } def resolve_phantom_gem_from_nexus_tag(nexus_tag: str) -> Path: """Resolve a NEXUS tag to its latest run dir and return phantom_gem.json path. Scans temp\\nexus_runs for directories named either: - <nexus_tag> - <TIMESTAMP>__<nexus_tag> Picks the lexicographically last match (latest run). Requires that phantom_gem.json exist in that directory. Raises FileNotFoundError with descriptive messages if anything is missing. """ base_dir = Path( r"E:\\__daniel347x\\__Obsidian\\__Inking into Mind\\--TypingMind\\Projects - All\\Projects - Individual\\TODO\\temp\\nexus_runs" ) if not base_dir.exists(): raise FileNotFoundError( "No NEXUS runs directory exists yet under temp\\nexus_runs; " "run nexus_scry(...), nexus_glimpse(...), or Cartographer first for this tag." ) suffix = f"__{nexus_tag}" candidates = [ child for child in base_dir.iterdir() if child.is_dir() and (child.name == nexus_tag or child.name.endswith(suffix)) ] if not candidates: raise FileNotFoundError( f"NEXUS tag '{nexus_tag}' has no run directory under temp\\nexus_runs; " "run nexus_scry(...), nexus_glimpse(..., mode='full'), or Cartographer first." ) run_dir = sorted(candidates, key=lambda p: p.name)[-1] phantom_path = run_dir / "phantom_gem.json" if not phantom_path.exists(): raise FileNotFoundError( f"NEXUS tag '{nexus_tag}' has no phantom_gem.json in {run_dir}; " "run nexus_ignite_shards(...) or nexus_glimpse(..., mode='full') first." ) return phantom_path if __name__ == "__main__": # Force UTF-8 stdout so unicode preview_tree (⦿, 🧨, emojis) never crashes under cp1252. # errors='replace' ensures we never throw UnicodeEncodeError in hostile terminals. if hasattr(sys.stdout, "reconfigure"): try: sys.stdout.reconfigure(encoding="utf-8", errors="replace") except Exception: pass if len(sys.argv) < 2: print(json.dumps({ "success": False, "error": "Usage: python jewelstrike.py <nexus-tag> [human-prefix] [--existing-jewel-from-exploration <jewel_json_path>]" })) sys.exit(1) nexus_tag = sys.argv[1] prefix: str | None = None existing_jewel: str | None = None # Parse optional human-prefix (positional) and override flag idx = 2 if idx < len(sys.argv) and not sys.argv[idx].startswith("--"): prefix = sys.argv[idx] idx += 1 while idx < len(sys.argv): arg = sys.argv[idx] if arg == "--existing-jewel-from-exploration" and idx + 1 < len(sys.argv): existing_jewel = sys.argv[idx + 1] idx += 2 else: idx += 1 try: phantom_gem_path = resolve_phantom_gem_from_nexus_tag(nexus_tag) except Exception as e: print(json.dumps({ "success": False, "error": str(e) })) sys.exit(1) # Optional: seed phantom_jewel.json from an existing JEWEL produced by # exploration FINALIZE. This avoids confusion where agents only have a GEM # but no JEWEL to anchor. if existing_jewel is not None: try: src = Path(existing_jewel).resolve() if not src.exists(): print(json.dumps({ "success": False, "error": f"Existing JEWEL file not found: {existing_jewel}" })) sys.exit(1) # Minimal sanity check: must be JSON and contain 'nodes' with open(src, "r", encoding="utf-8") as f: jewel_data = json.load(f) if not (isinstance(jewel_data, dict) and "nodes" in jewel_data): print(json.dumps({ "success": False, "error": "Existing JEWEL must be a JSON object with a 'nodes' key (exploration phantom_jewel style)" })) sys.exit(1) phantom_jewel_target = phantom_gem_path.with_name("phantom_jewel.json") shutil.copy2(src, phantom_jewel_target) except Exception as e: print(json.dumps({ "success": False, "error": f"Failed to seed phantom_jewel.json from existing JEWEL: {e}" })) sys.exit(1) result = jewelstrike(str(phantom_gem_path), prefix) # Ensure unicode (emoji/bullets) render as actual characters in console/tool output. print(json.dumps(result, indent=2, ensure_ascii=False)) if not result.get("success"): sys.exit(1)

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/daniel347x/workflowy-mcp-fixed'

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