@@ -241,15 +241,16 @@ static const tool_def_t TOOLS[] = {
241241 "{\"type\":\"integer\"},\"max_degree\":{\"type\":\"integer\"},\"exclude_entry_points\":{"
242242 "\"type\":\"boolean\"},\"include_connected\":{\"type\":\"boolean\"},\"limit\":{\"type\":"
243243 "\"integer\",\"description\":\"Max results. Default: "
244- "unlimited\"},\"offset\":{\"type\":\"integer\",\"default\":0}}}" },
244+ "unlimited\"},\"offset\":{\"type\":\"integer\",\"default\":0}},\"required\":[\"project\"] }" },
245245
246246 {"query_graph" ,
247247 "Execute a Cypher query against the knowledge graph for complex multi-hop patterns, "
248248 "aggregations, and cross-service analysis." ,
249249 "{\"type\":\"object\",\"properties\":{\"query\":{\"type\":\"string\",\"description\":\"Cypher "
250250 "query\"},\"project\":{\"type\":\"string\"},\"max_rows\":{\"type\":\"integer\","
251251 "\"description\":"
252- "\"Optional row limit. Default: unlimited (100k ceiling)\"}},\"required\":[\"query\"]}" },
252+ "\"Optional row limit. Default: unlimited (100k "
253+ "ceiling)\"}},\"required\":[\"query\",\"project\"]}" },
253254
254255 {"trace_call_path" ,
255256 "Trace function call paths — who calls a function and what it calls. Use INSTEAD OF grep when "
@@ -258,7 +259,7 @@ static const tool_def_t TOOLS[] = {
258259 "\"type\":\"string\"},\"direction\":{\"type\":\"string\",\"enum\":[\"inbound\",\"outbound\","
259260 "\"both\"],\"default\":\"both\"},\"depth\":{\"type\":\"integer\",\"default\":3},\"edge_"
260261 "types\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}}},\"required\":[\"function_"
261- "name\"]}" },
262+ "name\",\"project\" ]}" },
262263
263264 {"get_code_snippet" ,
264265 "Read source code for a function/class/symbol. IMPORTANT: First call search_graph to find the "
@@ -267,16 +268,17 @@ static const tool_def_t TOOLS[] = {
267268 "{\"type\":\"object\",\"properties\":{\"qualified_name\":{\"type\":\"string\",\"description\":"
268269 "\"Full qualified_name from search_graph, or short function name\"},\"project\":{"
269270 "\"type\":\"string\"},\"include_neighbors\":{"
270- "\"type\":\"boolean\",\"default\":false}},\"required\":[\"qualified_name\"]}" },
271+ "\"type\":\"boolean\",\"default\":false}},\"required\":[\"qualified_name\",\"project\" ]}" },
271272
272273 {"get_graph_schema" , "Get the schema of the knowledge graph (node labels, edge types)" ,
273- "{\"type\":\"object\",\"properties\":{\"project\":{\"type\":\"string\"}}}" },
274+ "{\"type\":\"object\",\"properties\":{\"project\":{\"type\":\"string\"}},\"required\":["
275+ "\"project\"]}" },
274276
275277 {"get_architecture" ,
276278 "Get high-level architecture overview — packages, services, dependencies, and project "
277279 "structure at a glance." ,
278280 "{\"type\":\"object\",\"properties\":{\"project\":{\"type\":\"string\"},\"aspects\":{\"type\":"
279- "\"array\",\"items\":{\"type\":\"string\"}}}}" },
281+ "\"array\",\"items\":{\"type\":\"string\"}}},\"required\":[\"project\"] }" },
280282
281283 {"search_code" ,
282284 "Graph-augmented code search. Finds text patterns via grep, then enriches results with "
@@ -294,31 +296,33 @@ static const tool_def_t TOOLS[] = {
294296 "(like grep -C). Only used in compact mode.\"},"
295297 "\"regex\":{\"type\":\"boolean\",\"default\":false},\"limit\":{\"type\":\"integer\","
296298 "\"description\":\"Max results (default 10)\",\"default\":10}},\"required\":["
297- "\"pattern\"]}" },
299+ "\"pattern\",\"project\" ]}" },
298300
299301 {"list_projects" , "List all indexed projects" , "{\"type\":\"object\",\"properties\":{}}" },
300302
301303 {"delete_project" , "Delete a project from the index" ,
302- "{\"type\":\"object\",\"properties\":{\"project_name \":{\"type\":\"string\"}},\"required\":["
303- "\"project_name \"]}" },
304+ "{\"type\":\"object\",\"properties\":{\"project \":{\"type\":\"string\"}},\"required\":["
305+ "\"project \"]}" },
304306
305307 {"index_status" , "Get the indexing status of a project" ,
306- "{\"type\":\"object\",\"properties\":{\"project\":{\"type\":\"string\"}}}" },
308+ "{\"type\":\"object\",\"properties\":{\"project\":{\"type\":\"string\"}},\"required\":["
309+ "\"project\"]}" },
307310
308311 {"detect_changes" , "Detect code changes and their impact" ,
309312 "{\"type\":\"object\",\"properties\":{\"project\":{\"type\":\"string\"},\"scope\":{\"type\":"
310313 "\"string\"},\"depth\":{\"type\":\"integer\",\"default\":2},\"base_branch\":{\"type\":"
311- "\"string\",\"default\":\"main\"}}}" },
314+ "\"string\",\"default\":\"main\"}},\"required\":[\"project\"] }" },
312315
313316 {"manage_adr" , "Create or update Architecture Decision Records" ,
314317 "{\"type\":\"object\",\"properties\":{\"project\":{\"type\":\"string\"},\"mode\":{\"type\":"
315318 "\"string\",\"enum\":[\"get\",\"update\",\"sections\"]},\"content\":{\"type\":\"string\"},"
316- "\"sections\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}}}}" },
319+ "\"sections\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}}},\"required\":[\"project\"]"
320+ "}" },
317321
318322 {"ingest_traces" , "Ingest runtime traces to enhance the knowledge graph" ,
319323 "{\"type\":\"object\",\"properties\":{\"traces\":{\"type\":\"array\",\"items\":{\"type\":"
320324 "\"object\"}},\"project\":{\"type\":"
321- "\"string\"}},\"required\":[\"traces\"]}" },
325+ "\"string\"}},\"required\":[\"traces\",\"project\" ]}" },
322326};
323327
324328static const int TOOL_COUNT = sizeof (TOOLS ) / sizeof (TOOLS [0 ]);
@@ -631,7 +635,7 @@ static const char *project_db_path(const char *project, char *buf, size_t bufsz)
631635 * Tracks last-access time so the event loop can evict idle stores. */
632636static cbm_store_t * resolve_store (cbm_mcp_server_t * srv , const char * project ) {
633637 if (!project ) {
634- return srv -> store ; /* no project specified → use whatever's open */
638+ return NULL ; /* project is required — no implicit fallback */
635639 }
636640
637641 srv -> store_last_used = time (NULL );
@@ -672,13 +676,66 @@ static cbm_store_t *resolve_store(cbm_mcp_server_t *srv, const char *project) {
672676 return srv -> store ;
673677}
674678
675- /* Bail with empty JSON result when no store is available. */
676- #define REQUIRE_STORE (store , project ) \
677- do { \
678- if (!(store)) { \
679- free(project); \
680- return cbm_mcp_text_result("{\"error\":\"no project loaded\"}", true); \
681- } \
679+ /* Build a helpful error listing available projects. Caller must free() result. */
680+ static char * build_project_list_error (const char * reason ) {
681+ char dir_path [1024 ];
682+ cache_dir (dir_path , sizeof (dir_path ));
683+
684+ /* Collect project names from .db files */
685+ char projects [4096 ] = "" ;
686+ int count = 0 ;
687+ cbm_dir_t * d = cbm_opendir (dir_path );
688+ if (d ) {
689+ int offset = 0 ;
690+ cbm_dirent_t * entry ;
691+ while ((entry = cbm_readdir (d )) != NULL ) {
692+ const char * n = entry -> name ;
693+ size_t len = strlen (n );
694+ if (len < 4 || strcmp (n + len - 3 , ".db" ) != 0 ) {
695+ continue ;
696+ }
697+ if (strncmp (n , "tmp-" , 4 ) == 0 || strncmp (n , "_" , 1 ) == 0 ) {
698+ continue ;
699+ }
700+ if (count > 0 && offset < (int )sizeof (projects ) - 2 ) {
701+ projects [offset ++ ] = ',' ;
702+ }
703+ int wrote = snprintf (projects + offset , sizeof (projects ) - (size_t )offset , "\"%.*s\"" ,
704+ (int )(len - 3 ), n );
705+ if (wrote > 0 ) {
706+ offset += wrote ;
707+ }
708+ count ++ ;
709+ }
710+ cbm_closedir (d );
711+ }
712+
713+ enum { ERR_BUF_SZ = 5120 };
714+ char buf [ERR_BUF_SZ ];
715+ if (count > 0 ) {
716+ snprintf (buf , sizeof (buf ),
717+ "{\"error\":\"%s\",\"hint\":\"Use list_projects to see all indexed projects, "
718+ "then pass the project name.\",\"available_projects\":[%s],\"count\":%d}" ,
719+ reason , projects , count );
720+ } else {
721+ snprintf (buf , sizeof (buf ),
722+ "{\"error\":\"%s\",\"hint\":\"No projects indexed yet. "
723+ "Call index_repository first.\"}" ,
724+ reason );
725+ }
726+ return heap_strdup (buf );
727+ }
728+
729+ /* Bail with project list when no store is available. */
730+ #define REQUIRE_STORE (store , project ) \
731+ do { \
732+ if (!(store)) { \
733+ char *_err = build_project_list_error("project not found or not indexed"); \
734+ char *_res = cbm_mcp_text_result(_err, true); \
735+ free(_err); \
736+ free(project); \
737+ return _res; \
738+ } \
682739 } while (0)
683740
684741/* ── Tool handler implementations ─────────────────────────────── */
@@ -778,9 +835,6 @@ static char *handle_list_projects(cbm_mcp_server_t *srv, const char *args) {
778835 * Callers that receive a non-NULL return value must free(project) themselves
779836 * before returning the error string. */
780837static char * verify_project_indexed (cbm_store_t * store , const char * project ) {
781- if (!project ) {
782- return NULL ; /* default project — always exists */
783- }
784838 cbm_project_t proj_check = {0 };
785839 if (cbm_store_get_project (store , project , & proj_check ) != CBM_STORE_OK ) {
786840 return cbm_mcp_text_result (
@@ -935,9 +989,12 @@ static char *handle_query_graph(cbm_mcp_server_t *srv, const char *args) {
935989 return cbm_mcp_text_result ("query is required" , true);
936990 }
937991 if (!store ) {
992+ char * _err = build_project_list_error ("project not found or not indexed" );
993+ char * _res = cbm_mcp_text_result (_err , true);
994+ free (_err );
938995 free (project );
939996 free (query );
940- return cbm_mcp_text_result ( "{\"error\":\"no project loaded\"}" , true) ;
997+ return _res ;
941998 }
942999
9431000 char * not_indexed = verify_project_indexed (store , project );
@@ -1024,9 +1081,9 @@ static char *handle_index_status(cbm_mcp_server_t *srv, const char *args) {
10241081
10251082/* delete_project: just erase the .db file (and WAL/SHM). */
10261083static char * handle_delete_project (cbm_mcp_server_t * srv , const char * args ) {
1027- char * name = cbm_mcp_get_string_arg (args , "project_name " );
1084+ char * name = cbm_mcp_get_string_arg (args , "project " );
10281085 if (!name ) {
1029- return cbm_mcp_text_result ("project_name is required" , true);
1086+ return cbm_mcp_text_result ("project is required" , true);
10301087 }
10311088
10321089 /* Close store if it's the project being deleted */
@@ -1151,10 +1208,13 @@ static char *handle_trace_call_path(cbm_mcp_server_t *srv, const char *args) {
11511208 return cbm_mcp_text_result ("function_name is required" , true);
11521209 }
11531210 if (!store ) {
1211+ char * _err = build_project_list_error ("project not found or not indexed" );
1212+ char * _res = cbm_mcp_text_result (_err , true);
1213+ free (_err );
11541214 free (func_name );
11551215 free (project );
11561216 free (direction );
1157- return cbm_mcp_text_result ( "{\"error\":\"no project loaded\"}" , true) ;
1217+ return _res ;
11581218 }
11591219
11601220 char * not_indexed = verify_project_indexed (store , project );
@@ -1650,9 +1710,12 @@ static char *handle_get_code_snippet(cbm_mcp_server_t *srv, const char *args) {
16501710
16511711 cbm_store_t * store = resolve_store (srv , project );
16521712 if (!store ) {
1713+ char * _err = build_project_list_error ("project not found or not indexed" );
1714+ char * _res = cbm_mcp_text_result (_err , true);
1715+ free (_err );
16531716 free (qn );
16541717 free (project );
1655- return cbm_mcp_text_result ( "{\"error\":\"no project loaded\"}" , true) ;
1718+ return _res ;
16561719 }
16571720
16581721 char * not_indexed = verify_project_indexed (store , project );
@@ -2014,17 +2077,25 @@ static char *handle_search_code(cbm_mcp_server_t *srv, const char *args) {
20142077 return cbm_mcp_text_result ("pattern is required" , true);
20152078 }
20162079
2017- /* Resolve project */
2018- if (!project && srv -> session_project [0 ]) {
2019- project = heap_strdup (srv -> session_project );
2080+ /* Project is required */
2081+ if (!project ) {
2082+ free (pattern );
2083+ free (file_pattern );
2084+ char * _err = build_project_list_error ("project is required" );
2085+ char * _res = cbm_mcp_text_result (_err , true);
2086+ free (_err );
2087+ return _res ;
20202088 }
20212089
20222090 char * root_path = get_project_root (srv , project );
20232091 if (!root_path ) {
20242092 free (pattern );
20252093 free (project );
20262094 free (file_pattern );
2027- return cbm_mcp_text_result ("project not found or not indexed" , true);
2095+ char * _err = build_project_list_error ("project not found or not indexed" );
2096+ char * _res = cbm_mcp_text_result (_err , true);
2097+ free (_err );
2098+ return _res ;
20282099 }
20292100
20302101 /* Reject shell metacharacters in user-supplied arguments */
@@ -2073,11 +2144,10 @@ static char *handle_search_code(cbm_mcp_server_t *srv, const char *args) {
20732144
20742145 cbm_store_t * pre_store = resolve_store (srv , project );
20752146 if (pre_store ) {
2076- const char * pn = project ? project : srv -> session_project ;
20772147 char * * indexed_files = NULL ;
20782148 int indexed_count = 0 ;
2079- if (pn &&
2080- cbm_store_list_files ( pre_store , pn , & indexed_files , & indexed_count ) == CBM_STORE_OK &&
2149+ if (cbm_store_list_files ( pre_store , project , & indexed_files , & indexed_count ) ==
2150+ CBM_STORE_OK &&
20812151 indexed_count > 0 ) {
20822152 FILE * fl = fopen (filelist , "w" );
20832153 if (fl ) {
@@ -2173,7 +2243,6 @@ static char *handle_search_code(cbm_mcp_server_t *srv, const char *args) {
21732243 * Then: one SQL query per unique file for nodes, one batch query for all degrees. */
21742244
21752245 cbm_store_t * store = resolve_store (srv , project );
2176- const char * proj_name = project ? project : srv -> session_project ;
21772246
21782247 int sr_cap = 32 ;
21792248 int sr_count = 0 ;
@@ -2201,8 +2270,8 @@ static char *handle_search_code(cbm_mcp_server_t *srv, const char *args) {
22012270 /* One SQL query: load all nodes in this file */
22022271 cbm_node_t * file_nodes = NULL ;
22032272 int file_node_count = 0 ;
2204- if (store && proj_name ) {
2205- cbm_store_find_nodes_by_file (store , proj_name , cur_file , & file_nodes , & file_node_count );
2273+ if (store ) {
2274+ cbm_store_find_nodes_by_file (store , project , cur_file , & file_nodes , & file_node_count );
22062275 }
22072276
22082277 /* Match each grep hit to tightest containing node (in-memory) */
0 commit comments