| 2730 | static bool sg_field_blocked(const char *f); /* internal-only fields, defined with search_graph */ |
| 2731 | |
| 2732 | static char *handle_get_graph_schema(cbm_mcp_server_t *srv, const char *args) { |
| 2733 | char *project = get_project_arg(args); |
| 2734 | cbm_store_t *store = resolve_store(srv, project); |
| 2735 | REQUIRE_STORE(store, project); |
| 2736 | |
| 2737 | char *not_indexed = verify_project_indexed(store, project); |
| 2738 | if (not_indexed) { |
| 2739 | free(project); |
| 2740 | return not_indexed; |
| 2741 | } |
| 2742 | |
| 2743 | cbm_schema_info_t schema = {0}; |
| 2744 | cbm_store_get_schema(store, project, &schema); |
| 2745 | |
| 2746 | yyjson_mut_doc *doc = yyjson_mut_doc_new(NULL); |
| 2747 | yyjson_mut_val *root = yyjson_mut_obj(doc); |
| 2748 | yyjson_mut_doc_set_root(doc, root); |
| 2749 | |
| 2750 | yyjson_mut_val *labels = yyjson_mut_arr(doc); |
| 2751 | for (int i = 0; i < schema.node_label_count; i++) { |
| 2752 | yyjson_mut_val *lbl = yyjson_mut_obj(doc); |
| 2753 | yyjson_mut_obj_add_str(doc, lbl, "label", schema.node_labels[i].label); |
| 2754 | yyjson_mut_obj_add_int(doc, lbl, "count", schema.node_labels[i].count); |
| 2755 | yyjson_mut_val *props = yyjson_mut_arr(doc); |
| 2756 | for (int j = 0; j < schema.node_labels[i].property_count; j++) { |
| 2757 | /* Internal similarity intermediates (fp/sp/bt) are blocked from every |
| 2758 | * tool response — advertising them here invited agents to request |
| 2759 | * fields the server then silently refuses. Filter them from the |
| 2760 | * schema so it only describes obtainable properties. */ |
| 2761 | if (sg_field_blocked(schema.node_labels[i].properties[j])) { |
| 2762 | continue; |
| 2763 | } |
| 2764 | yyjson_mut_arr_add_str(doc, props, schema.node_labels[i].properties[j]); |
| 2765 | } |
| 2766 | yyjson_mut_obj_add_val(doc, lbl, "properties", props); |
| 2767 | yyjson_mut_arr_add_val(labels, lbl); |
| 2768 | } |
| 2769 | yyjson_mut_obj_add_val(doc, root, "node_labels", labels); |
| 2770 | |
| 2771 | yyjson_mut_val *types = yyjson_mut_arr(doc); |
| 2772 | for (int i = 0; i < schema.edge_type_count; i++) { |
| 2773 | yyjson_mut_val *typ = yyjson_mut_obj(doc); |
| 2774 | yyjson_mut_obj_add_str(doc, typ, "type", schema.edge_types[i].type); |
| 2775 | yyjson_mut_obj_add_int(doc, typ, "count", schema.edge_types[i].count); |
| 2776 | yyjson_mut_val *eprops = yyjson_mut_arr(doc); |
| 2777 | for (int j = 0; j < schema.edge_types[i].property_count; j++) { |
| 2778 | yyjson_mut_arr_add_str(doc, eprops, schema.edge_types[i].properties[j]); |
| 2779 | } |
| 2780 | yyjson_mut_obj_add_val(doc, typ, "properties", eprops); |
| 2781 | yyjson_mut_arr_add_val(types, typ); |
| 2782 | } |
| 2783 | yyjson_mut_obj_add_val(doc, root, "edge_types", types); |
| 2784 | |
| 2785 | /* Check ADR presence */ |
| 2786 | cbm_project_t proj_info = {0}; |
| 2787 | if (cbm_store_get_project(store, project, &proj_info) == 0 && proj_info.root_path) { |
| 2788 | bool adr_exists = project_has_adr(store, project, proj_info.root_path); |
| 2789 | yyjson_mut_obj_add_bool(doc, root, "adr_present", adr_exists); |
no test coverage detected