diff --git a/src/mcp/mcp.c b/src/mcp/mcp.c index 42d334c34..a41205380 100644 --- a/src/mcp/mcp.c +++ b/src/mcp/mcp.c @@ -9214,6 +9214,21 @@ static void build_resource_schema(yyjson_mut_doc *doc, yyjson_mut_val *root, } yyjson_mut_obj_add_val(doc, root, "edge_types", type_arr); cbm_store_schema_free(&schema); + + bool overlay_limitation_reported = add_canonical_only_overlay_freshness( + doc, root, store, proj, + "codebase://schema reads canonical graph schema counts; ready overlay rows are not " + "included until active schema resource views or compaction are available."); + int dirty_pending = 0; + int dirty_overlay_ready = 0; + if (get_dirty_file_counts(store, proj, &dirty_pending, &dirty_overlay_ready)) { + add_dirty_file_freshness_counts(doc, root, dirty_pending, dirty_overlay_ready); + if (!overlay_limitation_reported) { + add_response_warning(doc, root, + "codebase://schema reads canonical graph schema counts; dirty " + "file changes may be absent until overlay or reindex completes."); + } + } } /* CBM_CONFIG_KEY_FUNCTIONS_EXCLUDE defined in constants section at top of file */ diff --git a/tests/test_mcp.c b/tests/test_mcp.c index 88b39420b..eadbaee55 100644 --- a/tests/test_mcp.c +++ b/tests/test_mcp.c @@ -2636,6 +2636,57 @@ TEST(resource_architecture_reports_ready_overlay_canonical_only) { PASS(); } +TEST(resource_schema_reports_ready_overlay_canonical_only) { + cbm_mcp_server_t *srv = cbm_mcp_server_new(NULL); + ASSERT_NOT_NULL(srv); + cbm_store_t *st = cbm_mcp_server_store(srv); + ASSERT_NOT_NULL(st); + + const char *proj = "resource-schema-overlay"; + ASSERT_EQ(cbm_store_upsert_project(st, proj, "/tmp/resource-schema-overlay"), CBM_STORE_OK); + cbm_mcp_server_set_project(srv, proj); + + cbm_node_t old_fn = {.project = proj, + .label = "Function", + .name = "OldResourceSchema", + .qualified_name = "resource.schema.OldResourceSchema", + .file_path = "src/main.c"}; + ASSERT_GT(cbm_store_upsert_node(st, &old_fn), 0); + + int64_t overlay_generation = 0; + ASSERT_EQ(cbm_store_reserve_overlay_generation(st, proj, 1, &overlay_generation), + CBM_STORE_OK); + cbm_node_t fresh_class = {.project = proj, + .label = "Class", + .name = "FreshResourceSchema", + .qualified_name = "resource.schema.FreshResourceSchema", + .file_path = "src/main.c", + .properties_json = "{}"}; + cbm_store_file_delta_t delta = {.project = proj, + .rel_path = "src/main.c", + .generation = 1, + .nodes = &fresh_class, + .node_count = 1}; + ASSERT_EQ(cbm_store_publish_overlay_file_delta(st, &delta, overlay_generation), + CBM_STORE_OK); + + char *resp = cbm_mcp_server_handle( + srv, "{\"jsonrpc\":\"2.0\",\"id\":100,\"method\":\"resources/read\"," + "\"params\":{\"uri\":\"codebase://schema\"}}"); + ASSERT_NOT_NULL(resp); + ASSERT_NOT_NULL(strstr(resp, "\"contents\"")); + ASSERT_NOT_NULL(strstr(resp, "\\\"label\\\":\\\"Function\\\"")); + ASSERT_NULL(strstr(resp, "\\\"label\\\":\\\"Class\\\"")); + ASSERT_NOT_NULL(strstr(resp, "codebase://schema reads canonical graph schema counts")); + ASSERT_NOT_NULL(strstr(resp, "ready overlay rows are not included")); + ASSERT_NOT_NULL(strstr(resp, "\\\"read_model\\\":\\\"canonical_only\\\"")); + ASSERT_NOT_NULL(strstr(resp, "\\\"active_file_tombstones\\\":1")); + + free(resp); + cbm_mcp_server_free(srv); + PASS(); +} + TEST(tool_get_architecture_path_scoping) { cbm_mcp_server_t *srv = cbm_mcp_server_new(NULL); ASSERT_NOT_NULL(srv); @@ -4987,6 +5038,7 @@ SUITE(mcp) { RUN_TEST(tool_get_architecture_uses_overlay_active_routes); RUN_TEST(tool_get_architecture_uses_overlay_active_file_summaries); RUN_TEST(resource_architecture_reports_ready_overlay_canonical_only); + RUN_TEST(resource_schema_reports_ready_overlay_canonical_only); RUN_TEST(tool_get_architecture_path_scoping); RUN_TEST(tool_query_graph_missing_query);