| 2646 | static bool sg_field_blocked(const char *f); /* internal-only fields, defined with search_graph */ |
| 2647 | |
| 2648 | static char *handle_get_graph_schema(cbm_mcp_server_t *srv, const char *args) { |
| 2649 | char *project = get_project_arg(args); |
| 2650 | cbm_store_t *store = resolve_store(srv, project); |
| 2651 | REQUIRE_STORE(store, project); |
| 2652 | |
| 2653 | char *not_indexed = verify_project_indexed(store, project); |
| 2654 | if (not_indexed) { |
| 2655 | free(project); |
| 2656 | return not_indexed; |
| 2657 | } |
| 2658 | |
| 2659 | cbm_schema_info_t schema = {0}; |
| 2660 | cbm_store_get_schema(store, project, &schema); |
| 2661 | |
| 2662 | yyjson_mut_doc *doc = yyjson_mut_doc_new(NULL); |
| 2663 | yyjson_mut_val *root = yyjson_mut_obj(doc); |
| 2664 | yyjson_mut_doc_set_root(doc, root); |
| 2665 | |
| 2666 | yyjson_mut_val *labels = yyjson_mut_arr(doc); |
| 2667 | for (int i = 0; i < schema.node_label_count; i++) { |
| 2668 | yyjson_mut_val *lbl = yyjson_mut_obj(doc); |
| 2669 | yyjson_mut_obj_add_str(doc, lbl, "label", schema.node_labels[i].label); |
| 2670 | yyjson_mut_obj_add_int(doc, lbl, "count", schema.node_labels[i].count); |
| 2671 | yyjson_mut_val *props = yyjson_mut_arr(doc); |
| 2672 | for (int j = 0; j < schema.node_labels[i].property_count; j++) { |
| 2673 | /* Internal similarity intermediates (fp/sp/bt) are blocked from every |
| 2674 | * tool response — advertising them here invited agents to request |
| 2675 | * fields the server then silently refuses. Filter them from the |
| 2676 | * schema so it only describes obtainable properties. */ |
| 2677 | if (sg_field_blocked(schema.node_labels[i].properties[j])) { |
| 2678 | continue; |
| 2679 | } |
| 2680 | yyjson_mut_arr_add_str(doc, props, schema.node_labels[i].properties[j]); |
| 2681 | } |
| 2682 | yyjson_mut_obj_add_val(doc, lbl, "properties", props); |
| 2683 | yyjson_mut_arr_add_val(labels, lbl); |
| 2684 | } |
| 2685 | yyjson_mut_obj_add_val(doc, root, "node_labels", labels); |
| 2686 | |
| 2687 | yyjson_mut_val *types = yyjson_mut_arr(doc); |
| 2688 | for (int i = 0; i < schema.edge_type_count; i++) { |
| 2689 | yyjson_mut_val *typ = yyjson_mut_obj(doc); |
| 2690 | yyjson_mut_obj_add_str(doc, typ, "type", schema.edge_types[i].type); |
| 2691 | yyjson_mut_obj_add_int(doc, typ, "count", schema.edge_types[i].count); |
| 2692 | yyjson_mut_val *eprops = yyjson_mut_arr(doc); |
| 2693 | for (int j = 0; j < schema.edge_types[i].property_count; j++) { |
| 2694 | yyjson_mut_arr_add_str(doc, eprops, schema.edge_types[i].properties[j]); |
| 2695 | } |
| 2696 | yyjson_mut_obj_add_val(doc, typ, "properties", eprops); |
| 2697 | yyjson_mut_arr_add_val(types, typ); |
| 2698 | } |
| 2699 | yyjson_mut_obj_add_val(doc, root, "edge_types", types); |
| 2700 | |
| 2701 | /* Check ADR presence */ |
| 2702 | cbm_project_t proj_info = {0}; |
| 2703 | if (cbm_store_get_project(store, project, &proj_info) == 0 && proj_info.root_path) { |
| 2704 | bool adr_exists = project_has_adr(store, project, proj_info.root_path); |
| 2705 | yyjson_mut_obj_add_bool(doc, root, "adr_present", adr_exists); |
no test coverage detected