Forráskód Böngészése

Delegate garden cycle helpers to domain plugin

Lukas Goldschmidt 1 hónapja
szülő
commit
cb2d8e671a
3 módosított fájl, 20 hozzáadás és 97 törlés
  1. 1 1
      README.md
  2. 19 20
      test.sh
  3. 0 76
      virtuoso_mcp.py

+ 1 - 1
README.md

@@ -91,4 +91,4 @@ Keep ontology discovery in `virtuoso_mcp` so any specialized layer (garden, inve
 
 ## Domain plugin layers
 
-To expose domain-specific helpers automatically, set the `DOMAIN_LAYERS` environment variable to a comma-separated list of Python modules (the default is `garden_layer.plugin`). Each module must expose a `register_layer(tools)` function that receives the MCP `TOOLS` dictionary and adds prefixed entries (e.g., `garden_add_seedling`). `virtuoso_mcp` calls those hooks at startup, so simply `pip install --upgrade git+https://repo.home.world.eu.org/lucky/garden_layer.git` and update `DOMAIN_LAYERS` to include `garden_layer.plugin`. The new tools appear in the `/mcp` tool list (`curl -sS http://127.0.0.1:8501/ | jq .tools`) without changing the single `/mcp` endpoint surface.
+To expose domain-specific helpers automatically, set the `DOMAIN_LAYERS` environment variable to a comma-separated list of Python modules (the default is `garden_layer.plugin`). Each module must expose a `register_layer(tools)` function that receives the MCP `TOOLS` dictionary and adds prefixed entries (e.g., `garden_add_seedling`). `virtuoso_mcp` calls those hooks at startup, so simply `pip install --upgrade git+https://repo.home.world.eu.org/lucky/garden_layer.git` and update `DOMAIN_LAYERS` to include `garden_layer.plugin`. The workspace already contains the `garden_layer` source tree (`workspace/garden_layer`), so during local iteration you can also install it in editable form: `pip install -e /home/lucky/.openclaw/workspace/garden_layer`. The new tools appear in the `/mcp` tool list (`curl -sS http://127.0.0.1:8501/ | jq .tools`) without changing the single `/mcp` endpoint surface.

+ 19 - 20
test.sh

@@ -92,27 +92,26 @@ if assert_tool_ok "list_graphs" '{"tool":"list_graphs","input":{}}'; then
   echo "$TOOL_LAST_RESPONSE" | jq -r '.result.results.bindings[].g.value' | sed 's/^/  - /' || true
 fi
 
-section "Update path (single triple + small Turtle)"
-if [[ "${MCP_ALLOW_EXAMPLE_LOAD:-false}" == "true" ]]; then
-  TOOL_LAST_RESPONSE=""
-  if assert_tool_ok "insert_triple" '{"tool":"insert_triple","input":{"subject":"http://world.eu.org/example1#TestPlant1","predicate":"http://www.w3.org/1999/02/22-rdf-syntax-ns#type","object":"http://world.eu.org/cannabis-breeding#IndividualPlant","object_type":"uri","graph":"http://world.eu.org/cannabis-breeding#test"}}'; then
-    echo "Inserted query preview:"
-    echo "$TOOL_LAST_RESPONSE" | jq -r '.query' | sed 's/^/  /'
-  fi
+section "Update path (fabricated triples)"
+TOOL_LAST_RESPONSE=""
+if assert_tool_ok "insert_triple" '{"tool":"insert_triple","input":{"subject":"http://example.org/plain#TestPlant1","predicate":"http://www.w3.org/1999/02/22-rdf-syntax-ns#type","object":"http://example.org/plain#Specimen","object_type":"uri","graph":"http://world.eu.org/cannabis-breeding#test"}}'; then
+  echo "Inserted query preview:"
+  echo "$TOOL_LAST_RESPONSE" | jq -r '.query' | sed 's/^/  /'
+fi
 
-  TOOL_LAST_RESPONSE=""
-  if assert_tool_ok "load_examples (productioncycle_export.ttl)" '{"tool":"load_examples","input":{"files":["productioncycle_export.ttl"],"graph":"http://world.eu.org/cannabis-breeding#test"}}'; then
-    echo "Loaded files:"
-    echo "$TOOL_LAST_RESPONSE" | jq -r '.result.loaded[] | "  - \(.file) -> \(.graph)"'
-  fi
+EXAMPLE_TTL=$(cat <<'EOF'
+@prefix ex: <http://example.org/plain#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+ex:PlainPlant a ex:Specimen ;
+    rdfs:label "Plain test plant" .
+EOF
+)
 
-  TOOL_LAST_RESPONSE=""
-  if assert_tool_ok "load_examples (export large TTL)" '{"tool":"load_examples","input":{"files":["export_2026-03-26T14_10_20.ttl"],"graph":"http://world.eu.org/cannabis-breeding#test"}}'; then
-    echo "Loaded files:"
-    echo "$TOOL_LAST_RESPONSE" | jq -r '.result.loaded[] | "  - \(.file) -> \(.graph)"'
-  fi
-else
-  echo "ℹ️  Skipped update tests (MCP_ALLOW_EXAMPLE_LOAD != true)"
+payload=$(jq -n --arg ttl "$EXAMPLE_TTL" --arg graph "$TEST_GRAPH" '{"tool":"batch_insert","input":{"ttl":$ttl,"graph":$graph}}')
+TOOL_LAST_RESPONSE=""
+if assert_tool_ok "batch_insert (fabricated TTL)" "$payload"; then
+  echo "Batch insert query:"
+  echo "$TOOL_LAST_RESPONSE" | jq -r '.result.query' | sed 's/^/  /'
 fi
 
 section "Helper retrieval checks"
@@ -190,7 +189,7 @@ if assert_tool_ok "property_usage_statistics" '{"tool":"property_usage_statistic
 fi
 
 TOOL_LAST_RESPONSE=""
-if assert_tool_ok "batch_insert(test)" '{"tool":"batch_insert","input":{"ttl":"<http://world.eu.org/example1#batch_test_subject> <http://www.w3.org/2000/01/rdf-schema#label> \"batch helper\" .","graph":"http://world.eu.org/cannabis-breeding#test"}}'; then
+if assert_tool_ok "batch_insert(test)" '{"tool":"batch_insert","input":{"ttl":"<http://example.org/plain#BatchPlant> <http://www.w3.org/2000/01/rdf-schema#label> \"batch helper\" .","graph":"http://world.eu.org/cannabis-breeding#test"}}'; then
   echo "Batch insert query:"
   echo "$TOOL_LAST_RESPONSE" | jq -r '.result.query' | sed 's/^/  /'
 fi

+ 0 - 76
virtuoso_mcp.py

@@ -262,23 +262,6 @@ def tool_get_entities_by_type(input_data: Dict[str, Any]) -> Dict[str, Any]:
     return run_sparql(query)
 
 
-def tool_cycle_plants(input_data: Dict[str, Any]) -> Dict[str, Any]:
-    cycle_uri = input_data.get("cycle_uri")
-    if not cycle_uri:
-        raise ValueError("Missing 'cycle_uri' field")
-    limit = int(input_data.get("limit", 50))
-    limit = min(max(limit, 1), SPARQL_MAX_LIMIT)
-    query = f"""
-    SELECT ?plant ?plantLabel ?parent WHERE {{
-        ?plant <{IN_CYCLE}> <{cycle_uri}> .
-        OPTIONAL {{ ?plant rdfs:label ?plantLabel }}
-        OPTIONAL {{ ?plant <{CLONE_OF}> ?parent }}
-    }}
-    LIMIT {limit}
-    """
-    return run_sparql(query)
-
-
 def tool_get_predicates_for_subject(input_data: Dict[str, Any]) -> Dict[str, Any]:
     subject_uri = input_data.get("subject_uri")
     if not subject_uri:
@@ -594,34 +577,6 @@ def tool_batch_insert(input_data: Dict[str, Any]) -> Dict[str, Any]:
     return {**result, "query": query}
 
 
-def tool_reassign_cycle(input_data: Dict[str, Any]) -> Dict[str, Any]:
-    subject = input_data.get("subject")
-    new_cycle = input_data.get("new_cycle")
-    old_cycle = input_data.get("old_cycle")
-    graph = input_data.get("graph") or GRAPH_URI
-
-    if not subject or not new_cycle:
-        raise ValueError("Provide 'subject' and 'new_cycle' fields")
-
-    if old_cycle:
-        delete_clause = f"<{subject}> <{IN_CYCLE}> <{old_cycle}> ."
-        where_clause = delete_clause
-        update_query = f"""
-        WITH <{graph}>
-        DELETE {{ {delete_clause} }}
-        INSERT {{ <{subject}> <{IN_CYCLE}> <{new_cycle}> . }}
-        WHERE {{ {where_clause} }}
-        """
-    else:
-        update_query = f"""
-        WITH <{graph}>
-        INSERT {{ <{subject}> <{IN_CYCLE}> <{new_cycle}> . }}
-        WHERE {{ }}
-        """
-
-    return run_sparql_update(update_query)
-
-
 def tool_insert_triple(input_data: Dict[str, Any]) -> Dict[str, Any]:
     subject = input_data.get("subject")
     predicate = input_data.get("predicate")
@@ -659,40 +614,12 @@ def tool_insert_triple(input_data: Dict[str, Any]) -> Dict[str, Any]:
     return {**result, "query": update_query}
 
 
-def tool_load_examples(input_data: Dict[str, Any]) -> Dict[str, Any]:
-    if not ALLOW_EXAMPLE_LOAD:
-        raise HTTPException(status_code=403, detail="Example loading is disabled")
-
-    files = input_data.get("files") or []
-    if isinstance(files, str):
-        files = [files]
-    if not files:
-        files = [p.name for p in EXAMPLES_DIR.glob("*.ttl")]
-
-    graph = input_data.get("graph") or EXAMPLE_GRAPH
-    results = []
-
-    for filename in files:
-        file_path = (EXAMPLES_DIR / filename).resolve()
-        if not file_path.exists():
-            raise HTTPException(status_code=400, detail=f"Missing example file: {filename}")
-        if EXAMPLES_DIR not in file_path.parents:
-            raise HTTPException(status_code=400, detail="Invalid example file path")
-        ttl_text = file_path.read_text(encoding="utf-8")
-        update_query = ttl_to_sparql_insert(ttl_text, graph)
-        run_sparql_update(update_query)
-        results.append({"file": filename, "graph": graph})
-
-    return {"loaded": results}
-
-
 # --- TOOL REGISTRY ---
 TOOLS = {
     "sparql_query": tool_sparql_query,
     "list_graphs": tool_list_graphs,
     "search_label": tool_search_label,
     "get_entities_by_type": tool_get_entities_by_type,
-    "cycle_plants": tool_cycle_plants,
     "get_predicates_for_subject": tool_get_predicates_for_subject,
     "get_labels_for_subject": tool_get_labels_for_subject,
     "traverse_property": tool_traverse_property,
@@ -704,9 +631,7 @@ TOOLS = {
     "path_traverse": tool_path_traverse,
     "property_usage_statistics": tool_property_usage_statistics,
     "batch_insert": tool_batch_insert,
-    "reassign_cycle": tool_reassign_cycle,
     "insert_triple": tool_insert_triple,
-    "load_examples": tool_load_examples,
 }
 
 
@@ -767,7 +692,6 @@ TOOL_DOCS = {
     "batch_insert": "Insert multiple triples or TTL at once with a single guarded update.",
     "reassign_cycle": "Move a subject to another production cycle by updating its inCycle link.",
     "insert_triple": "Insert a single triple (useful for debugging updates).",
-    "load_examples": "Load Turtle examples from the local examples/ directory into a graph.",
 }