@@ -226,6 +226,15 @@ static size_t estimate_search_result_chars(const cbm_search_result_t *sr, bool c
226226 } else {
227227 size += 24 ;
228228 }
229+ /* Account for connected_names array if populated (mirrors add_search_result_item) */
230+ if (sr -> connected_count > 0 && sr -> connected_names ) {
231+ size += 24 ; /* "connected_names":[] overhead */
232+ for (int i = 0 ; i < sr -> connected_count ; i ++ ) {
233+ if (sr -> connected_names [i ]) {
234+ size += strlen (sr -> connected_names [i ]) + 4 ; /* "name", */
235+ }
236+ }
237+ }
229238 return size ;
230239}
231240
@@ -254,6 +263,17 @@ static void add_search_result_item(yyjson_mut_doc *doc, yyjson_mut_val *results,
254263 yyjson_mut_obj_add_real (doc , item , "pagerank" , sr -> pagerank );
255264 }
256265
266+ /* Include connected node names if populated */
267+ if (sr -> connected_count > 0 && sr -> connected_names ) {
268+ yyjson_mut_val * conn = yyjson_mut_arr (doc );
269+ for (int i = 0 ; i < sr -> connected_count ; i ++ ) {
270+ if (sr -> connected_names [i ]) {
271+ yyjson_mut_arr_add_strcpy (doc , conn , sr -> connected_names [i ]);
272+ }
273+ }
274+ yyjson_mut_obj_add_val (doc , item , "connected_names" , conn );
275+ }
276+
257277 yyjson_mut_arr_add_val (results , item );
258278}
259279
@@ -1081,6 +1101,46 @@ static bool cbm_mcp_get_bool_arg_default(const char *args_json, const char *key,
10811101 return result ;
10821102}
10831103
1104+ /* Extract a JSON string array. Returns heap-allocated array of heap strings.
1105+ * Sets *out_count. Returns NULL only when the key is absent or not an array.
1106+ * For an empty array [], returns a non-NULL pointer with *out_count = 0
1107+ * so callers can distinguish "not provided" from "explicitly empty". */
1108+ static char * * cbm_mcp_get_string_array_arg (const char * args_json , const char * key , int * out_count ) {
1109+ * out_count = 0 ;
1110+ yyjson_doc * doc = yyjson_read (args_json , strlen (args_json ), 0 );
1111+ if (!doc ) {
1112+ return NULL ;
1113+ }
1114+ yyjson_val * root = yyjson_doc_get_root (doc );
1115+ yyjson_val * arr = yyjson_obj_get (root , key );
1116+ if (!arr || !yyjson_is_arr (arr )) {
1117+ yyjson_doc_free (doc );
1118+ return NULL ;
1119+ }
1120+ int count = (int )yyjson_arr_size (arr );
1121+ if (count == 0 ) {
1122+ /* Key present but empty array — return non-NULL sentinel with count=0 */
1123+ yyjson_doc_free (doc );
1124+ return calloc (1 , sizeof (char * )); /* non-NULL zero-initialized, caller frees */
1125+ }
1126+ char * * result = malloc ((size_t )count * sizeof (char * ));
1127+ int n = 0 ;
1128+ size_t idx , max ;
1129+ yyjson_val * val ;
1130+ yyjson_arr_foreach (arr , idx , max , val ) {
1131+ if (yyjson_is_str (val )) {
1132+ result [n ++ ] = heap_strdup (yyjson_get_str (val ));
1133+ }
1134+ }
1135+ yyjson_doc_free (doc );
1136+ if (n == 0 ) {
1137+ /* Array had elements but none were strings — still "provided" */
1138+ return result ; /* non-NULL, count stays 0 */
1139+ }
1140+ * out_count = n ;
1141+ return result ;
1142+ }
1143+
10841144/* ══════════════════════════════════════════════════════════════════
10851145 * MCP SERVER
10861146 * ══════════════════════════════════════════════════════════════════ */
@@ -1553,6 +1613,9 @@ static char *handle_search_graph(cbm_mcp_server_t *srv, const char *args) {
15531613 char * name_pattern = cbm_mcp_get_string_arg (args , "name_pattern" );
15541614 char * qn_pattern = cbm_mcp_get_string_arg (args , "qn_pattern" );
15551615 char * file_pattern = cbm_mcp_get_string_arg (args , "file_pattern" );
1616+ char * relationship = cbm_mcp_get_string_arg (args , "relationship" );
1617+ bool exclude_entry_points = cbm_mcp_get_bool_arg (args , "exclude_entry_points" );
1618+ bool include_connected = cbm_mcp_get_bool_arg (args , "include_connected" );
15561619 int limit = cbm_mcp_get_int_arg (args , "limit" , 500000 );
15571620 int offset = cbm_mcp_get_int_arg (args , "offset" , 0 );
15581621 int min_degree = cbm_mcp_get_int_arg (args , "min_degree" , -1 );
@@ -1567,6 +1630,9 @@ static char *handle_search_graph(cbm_mcp_server_t *srv, const char *args) {
15671630 .name_pattern = name_pattern ,
15681631 .qn_pattern = qn_pattern ,
15691632 .file_pattern = file_pattern ,
1633+ .relationship = relationship ,
1634+ .exclude_entry_points = exclude_entry_points ,
1635+ .include_connected = include_connected ,
15701636 .limit = limit ,
15711637 .offset = offset ,
15721638 .min_degree = min_degree ,
@@ -1659,6 +1725,7 @@ static char *handle_search_graph(cbm_mcp_server_t *srv, const char *args) {
16591725 free (name_pattern );
16601726 free (qn_pattern );
16611727 free (file_pattern );
1728+ free (relationship );
16621729
16631730 char * result = cbm_mcp_text_result (json , false);
16641731 free (json );
@@ -2447,9 +2514,15 @@ static char *handle_trace_call_path(cbm_mcp_server_t *srv, const char *args) {
24472514 bool ranked = cbm_mcp_get_bool_arg_default (args , "ranked" , true);
24482515 size_t char_budget = max_tokens_to_char_budget (max_tokens );
24492516
2517+ /* Extract edge_types array; fall back to {"CALLS"} if not provided */
2518+ int user_edge_type_count = 0 ;
2519+ char * * user_edge_types = cbm_mcp_get_string_array_arg (args , "edge_types" , & user_edge_type_count );
2520+
24502521 if (!func_name ) {
24512522 free (project );
24522523 free (direction );
2524+ for (int i = 0 ; i < user_edge_type_count ; i ++ ) free (user_edge_types [i ]);
2525+ free (user_edge_types );
24532526 return cbm_mcp_text_result ("function_name is required" , true);
24542527 }
24552528 if (!store ) {
@@ -2459,6 +2532,8 @@ static char *handle_trace_call_path(cbm_mcp_server_t *srv, const char *args) {
24592532 free (func_name );
24602533 free (project );
24612534 free (direction );
2535+ for (int i = 0 ; i < user_edge_type_count ; i ++ ) free (user_edge_types [i ]);
2536+ free (user_edge_types );
24622537 return _res ;
24632538 }
24642539
@@ -2467,6 +2542,8 @@ static char *handle_trace_call_path(cbm_mcp_server_t *srv, const char *args) {
24672542 free (func_name );
24682543 free (project );
24692544 free (direction );
2545+ for (int i = 0 ; i < user_edge_type_count ; i ++ ) free (user_edge_types [i ]);
2546+ free (user_edge_types );
24702547 return not_indexed ;
24712548 }
24722549
@@ -2490,6 +2567,8 @@ static char *handle_trace_call_path(cbm_mcp_server_t *srv, const char *args) {
24902567 free (func_name );
24912568 free (project );
24922569 free (direction );
2570+ for (int i = 0 ; i < user_edge_type_count ; i ++ ) free (user_edge_types [i ]);
2571+ free (user_edge_types );
24932572 cbm_store_free_nodes (nodes , 0 );
24942573 return cbm_mcp_text_result ("{\"error\":\"function not found\"}" , true);
24952574 }
@@ -2501,26 +2580,42 @@ static char *handle_trace_call_path(cbm_mcp_server_t *srv, const char *args) {
25012580 yyjson_mut_obj_add_str (doc , root , "function" , func_name );
25022581 yyjson_mut_obj_add_str (doc , root , "direction" , direction );
25032582
2504- const char * edge_types [] = {"CALLS" };
2505- int edge_type_count = 1 ;
2583+ /* Use user-provided edge_types or default to {"CALLS"}.
2584+ * user_edge_types non-NULL with count==0 means explicit empty array [] —
2585+ * honor it by using an empty edge list (BFS traverses nothing). */
2586+ const char * default_edge_types [] = {"CALLS" };
2587+ const char * * edge_types ;
2588+ int edge_type_count ;
2589+ if (user_edge_types ) {
2590+ edge_types = (const char * * )user_edge_types ;
2591+ edge_type_count = user_edge_type_count ;
2592+ } else {
2593+ edge_types = default_edge_types ;
2594+ edge_type_count = 1 ;
2595+ }
25062596
2507- /* Run BFS for each requested direction.
2508- * IMPORTANT: yyjson_mut_obj_add_str borrows pointers — we must keep
2509- * traversal results alive until after yy_doc_to_str serialization. */
2597+ /* Determine requested directions */
25102598 // NOLINTNEXTLINE(readability-implicit-bool-conversion)
2511- bool do_outbound = strcmp (direction , "outbound" ) == 0 || strcmp (direction , "both" ) == 0 ;
2599+ bool want_outbound = strcmp (direction , "outbound" ) == 0 || strcmp (direction , "both" ) == 0 ;
25122600 // NOLINTNEXTLINE(readability-implicit-bool-conversion)
2513- bool do_inbound = strcmp (direction , "inbound" ) == 0 || strcmp (direction , "both" ) == 0 ;
2601+ bool want_inbound = strcmp (direction , "inbound" ) == 0 || strcmp (direction , "both" ) == 0 ;
25142602
2603+ /* Run BFS for each requested direction.
2604+ * Skip BFS when edge_type_count == 0 (explicit empty array []),
2605+ * but still emit empty arrays to preserve the response schema.
2606+ * IMPORTANT: yyjson_mut_obj_add_str borrows pointers — we must keep
2607+ * traversal results alive until after yy_doc_to_str serialization. */
25152608 cbm_traverse_result_t tr_out = {0 };
25162609 cbm_traverse_result_t tr_in = {0 };
25172610
2518- if (do_outbound ) {
2519- cbm_store_bfs (store , nodes [0 ].id , "outbound" , edge_types , edge_type_count , depth , 100 ,
2520- & tr_out );
2521- if (ranked && tr_out .visited_count > 1 ) {
2522- qsort (tr_out .visited , (size_t )tr_out .visited_count , sizeof (cbm_node_hop_t ),
2523- node_hop_rank_cmp );
2611+ if (want_outbound ) {
2612+ if (edge_type_count > 0 ) {
2613+ cbm_store_bfs (store , nodes [0 ].id , "outbound" , edge_types , edge_type_count , depth , 100 ,
2614+ & tr_out );
2615+ if (ranked && tr_out .visited_count > 1 ) {
2616+ qsort (tr_out .visited , (size_t )tr_out .visited_count , sizeof (cbm_node_hop_t ),
2617+ node_hop_rank_cmp );
2618+ }
25242619 }
25252620
25262621 yyjson_mut_val * callees = yyjson_mut_arr (doc );
@@ -2530,12 +2625,14 @@ static char *handle_trace_call_path(cbm_mcp_server_t *srv, const char *args) {
25302625 yyjson_mut_obj_add_val (doc , root , "callees" , callees );
25312626 }
25322627
2533- if (do_inbound ) {
2534- cbm_store_bfs (store , nodes [0 ].id , "inbound" , edge_types , edge_type_count , depth , 100 ,
2535- & tr_in );
2536- if (ranked && tr_in .visited_count > 1 ) {
2537- qsort (tr_in .visited , (size_t )tr_in .visited_count , sizeof (cbm_node_hop_t ),
2538- node_hop_rank_cmp );
2628+ if (want_inbound ) {
2629+ if (edge_type_count > 0 ) {
2630+ cbm_store_bfs (store , nodes [0 ].id , "inbound" , edge_types , edge_type_count , depth , 100 ,
2631+ & tr_in );
2632+ if (ranked && tr_in .visited_count > 1 ) {
2633+ qsort (tr_in .visited , (size_t )tr_in .visited_count , sizeof (cbm_node_hop_t ),
2634+ node_hop_rank_cmp );
2635+ }
25392636 }
25402637
25412638 yyjson_mut_val * callers = yyjson_mut_arr (doc );
@@ -2561,18 +2658,18 @@ static char *handle_trace_call_path(cbm_mcp_server_t *srv, const char *args) {
25612658 yyjson_mut_obj_add_bool (doc , root , "truncated" , true);
25622659
25632660 int total_results = 0 ;
2564- if (do_outbound ) {
2661+ if (want_outbound ) {
25652662 total_results += tr_out .visited_count ;
25662663 }
2567- if (do_inbound ) {
2664+ if (want_inbound ) {
25682665 total_results += tr_in .visited_count ;
25692666 }
25702667 yyjson_mut_obj_add_int (doc , root , "total_results" , total_results );
25712668
25722669 size_t used = 96 + strlen (func_name ) + strlen (direction );
25732670 int shown = 0 ;
25742671
2575- if (do_outbound ) {
2672+ if (want_outbound ) {
25762673 yyjson_mut_val * callees = yyjson_mut_arr (doc );
25772674 int shown_callees = 0 ;
25782675 int full_callees = 0 ;
@@ -2607,7 +2704,7 @@ static char *handle_trace_call_path(cbm_mcp_server_t *srv, const char *args) {
26072704 }
26082705 }
26092706
2610- if (do_inbound ) {
2707+ if (want_inbound ) {
26112708 yyjson_mut_val * callers = yyjson_mut_arr (doc );
26122709 int shown_callers = 0 ;
26132710 int full_callers = 0 ;
@@ -2648,17 +2745,19 @@ static char *handle_trace_call_path(cbm_mcp_server_t *srv, const char *args) {
26482745 }
26492746
26502747 /* Now safe to free traversal data */
2651- if (do_outbound ) {
2748+ if (want_outbound ) {
26522749 cbm_store_traverse_free (& tr_out );
26532750 }
2654- if (do_inbound ) {
2751+ if (want_inbound ) {
26552752 cbm_store_traverse_free (& tr_in );
26562753 }
26572754
26582755 cbm_store_free_nodes (nodes , node_count );
26592756 free (func_name );
26602757 free (project );
26612758 free (direction );
2759+ for (int i = 0 ; i < user_edge_type_count ; i ++ ) free (user_edge_types [i ]);
2760+ free (user_edge_types );
26622761
26632762 char * result = cbm_mcp_text_result (json , false);
26642763 free (json );
0 commit comments