mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-09 09:11:00 +08:00
fix(mcp): report blocked automatic indexing by active tool mode
Previously, build_project_list_error_srv discarded whether automatic indexing was disabled or stopped by auto_index_limit and always recommended index_repository, even when streamlined tools/list hid that tool. Double-quoted path examples could also invalidate the nested structured JSON response. Record request-thread-owned block state in maybe_auto_index and cbm_mcp_auto_index_within_limit. mcp_index_recovery_action at src/mcp/mcp.c:3937 now uses the existing profile/reveal authorities: classic or revealed streamlined mode calls index_repository directly, unrevealed streamlined mode calls _hidden_tools and refreshes tools/list first, and profiles without that tool use the CLI. Existing synchronous first-use recovery still runs before any user action is emitted. Centralize streamlined/classic config values in src/cli/cli.h:383. tests/test_mcp.c:9617 covers auto_index=false, bounded file-limit rejection, classic, and revealed streamlined behavior; each response must parse as JSON with structuredContent and must not claim auto_indexing. Verified: focused ASan/UBSan test 1/1; scripts/check-source-safety.sh; git diff --check; git clang-format --diff; staged private-path scan. Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
This commit is contained in:
+3
-3
@@ -9751,7 +9751,7 @@ typedef struct {
|
||||
#define PRESET_COUNT(values_) (sizeof(values_) / sizeof((values_)[0]))
|
||||
|
||||
static const cbm_config_preset_value_t PRESET_STREAMLINED_QUALITY[] = {
|
||||
PRESET_VALUE(CBM_CONFIG_TOOL_MODE, "streamlined"),
|
||||
PRESET_VALUE(CBM_CONFIG_TOOL_MODE, CBM_CONFIG_TOOL_MODE_STREAMLINED),
|
||||
PRESET_VALUE(CBM_CONFIG_RANK_ENABLED, "true"),
|
||||
PRESET_VALUE(CBM_CONFIG_AUTO_INDEX_DEPS, "true"),
|
||||
PRESET_VALUE(CBM_CONFIG_SIMILARITY_ENABLED, "true"),
|
||||
@@ -9761,7 +9761,7 @@ static const cbm_config_preset_value_t PRESET_STREAMLINED_QUALITY[] = {
|
||||
};
|
||||
|
||||
static const cbm_config_preset_value_t PRESET_CLASSIC_QUALITY[] = {
|
||||
PRESET_VALUE(CBM_CONFIG_TOOL_MODE, "classic"),
|
||||
PRESET_VALUE(CBM_CONFIG_TOOL_MODE, CBM_CONFIG_TOOL_MODE_CLASSIC),
|
||||
PRESET_VALUE(CBM_CONFIG_RANK_ENABLED, "true"),
|
||||
PRESET_VALUE(CBM_CONFIG_AUTO_INDEX_DEPS, "true"),
|
||||
PRESET_VALUE(CBM_CONFIG_SIMILARITY_ENABLED, "true"),
|
||||
@@ -9988,7 +9988,7 @@ const cbm_config_entry_t CBM_CONFIG_REGISTRY[] = {
|
||||
"auto-pushed summary that closes the codebase://architecture pull-only gap. Kept small (10) "
|
||||
"to keep first-response token cost modest; raise to 20-25 if you want richer upfront context."},
|
||||
/* ── Tools ── */
|
||||
{CBM_CONFIG_TOOL_MODE, "streamlined", "CBM_TOOL_MODE", "Tools",
|
||||
{CBM_CONFIG_TOOL_MODE, CBM_CONFIG_TOOL_MODE_STREAMLINED, "CBM_TOOL_MODE", "Tools",
|
||||
"Which tool surface the MCP server lists by default",
|
||||
"streamlined|classic",
|
||||
"'streamlined' (default): lists core tools plus _hidden_tools discovery: "
|
||||
|
||||
@@ -380,6 +380,8 @@ int cbm_config_apply_preset(cbm_config_t *cfg, const char *name);
|
||||
#define CBM_CONFIG_SEARCH_LIMIT "search_limit"
|
||||
#define CBM_CONFIG_QUERY_MAX_ROWS "query_max_rows"
|
||||
#define CBM_CONFIG_TOOL_MODE "tool_mode"
|
||||
#define CBM_CONFIG_TOOL_MODE_STREAMLINED "streamlined"
|
||||
#define CBM_CONFIG_TOOL_MODE_CLASSIC "classic"
|
||||
#define CBM_CONFIG_DEFAULT_RESPONSE_FORMAT "default_response_format"
|
||||
#define CBM_DEFAULT_QUERY_MAX_ROWS 100000
|
||||
#define CBM_DEFAULT_QUERY_MAX_ROWS_STR "100000"
|
||||
|
||||
+108
-14
@@ -1435,9 +1435,6 @@ static const tool_def_t STREAMLINED_TOOLS[] = {
|
||||
};
|
||||
static const int STREAMLINED_TOOL_COUNT = sizeof(STREAMLINED_TOOLS) / sizeof(STREAMLINED_TOOLS[0]);
|
||||
|
||||
/* Config key for tool visibility mode. */
|
||||
#define CBM_CONFIG_TOOL_MODE "tool_mode"
|
||||
|
||||
static const char MCP_TOOL_OUTPUT_SCHEMA[] =
|
||||
"{\"type\":\"object\",\"additionalProperties\":true}";
|
||||
static const char MCP_HIDDEN_TOOL_INPUT_SCHEMA[] = "{\"type\":\"object\",\"properties\":{}}";
|
||||
@@ -2344,6 +2341,13 @@ static char *build_key_functions_sql(const char *exclude_csv, const char **exclu
|
||||
static bool validate_cbm_db_with_timeout(const char *path, int busy_timeout_ms);
|
||||
static void *overlay_compaction_thread(void *arg);
|
||||
|
||||
typedef enum {
|
||||
MCP_AUTOINDEX_BLOCK_NONE = 0,
|
||||
MCP_AUTOINDEX_BLOCK_DISABLED,
|
||||
MCP_AUTOINDEX_BLOCK_FILE_COUNT,
|
||||
MCP_AUTOINDEX_BLOCK_FILE_LIMIT,
|
||||
} mcp_autoindex_block_t;
|
||||
|
||||
struct cbm_mcp_server {
|
||||
cbm_mcp_tool_profile_t tool_profile;
|
||||
cbm_store_t *store; /* currently open project store (or NULL) */
|
||||
@@ -2366,6 +2370,12 @@ struct cbm_mcp_server {
|
||||
bool autoindex_active; /* true if auto-index thread was started */
|
||||
bool autoindex_failed; /* IX-1: true if last auto-index attempt failed */
|
||||
bool just_autoindexed; /* IX-3: true after auto-index completes, reset on next search */
|
||||
/* Request-thread-owned reason startup indexing did not start. This reports
|
||||
* only this server's decision; it never guesses sibling-process liveness
|
||||
* from temp files or other crash-stale filesystem artifacts. */
|
||||
mcp_autoindex_block_t autoindex_block;
|
||||
int autoindex_observed_files;
|
||||
int autoindex_file_limit;
|
||||
bool context_injected; /* true after first _context header sent (Phase 9) */
|
||||
/* Request-thread-owned tools/list docstring. Graph workers may only mark
|
||||
* it stale atomically; they must never read, replace, or free the pointer. */
|
||||
@@ -2425,12 +2435,12 @@ static bool cbm_mcp_tool_mode_is_classic(cbm_mcp_server_t *srv) {
|
||||
const char *tool_mode = cbm_safe_getenv("CBM_TOOL_MODE", tool_mode_buf,
|
||||
sizeof(tool_mode_buf), NULL);
|
||||
if (tool_mode && tool_mode[0] != '\0') {
|
||||
return strcmp(tool_mode, "classic") == 0;
|
||||
return strcmp(tool_mode, CBM_CONFIG_TOOL_MODE_CLASSIC) == 0;
|
||||
}
|
||||
tool_mode = (srv && srv->config)
|
||||
? cbm_config_get(srv->config, CBM_CONFIG_TOOL_MODE, "streamlined")
|
||||
: "streamlined";
|
||||
return strcmp(tool_mode, "classic") == 0;
|
||||
tool_mode = (srv && srv->config) ? cbm_config_get(srv->config, CBM_CONFIG_TOOL_MODE,
|
||||
CBM_CONFIG_TOOL_MODE_STREAMLINED)
|
||||
: CBM_CONFIG_TOOL_MODE_STREAMLINED;
|
||||
return strcmp(tool_mode, CBM_CONFIG_TOOL_MODE_CLASSIC) == 0;
|
||||
}
|
||||
|
||||
static cbm_mcp_output_format_t cbm_mcp_response_format(cbm_mcp_server_t *srv,
|
||||
@@ -2620,17 +2630,29 @@ static int cbm_mcp_auto_index_limit(cbm_mcp_server_t *srv) {
|
||||
|
||||
static bool cbm_mcp_auto_index_within_limit(cbm_mcp_server_t *srv, const char *root_path) {
|
||||
int file_limit = cbm_mcp_auto_index_limit(srv);
|
||||
if (srv) {
|
||||
srv->autoindex_block = MCP_AUTOINDEX_BLOCK_NONE;
|
||||
srv->autoindex_observed_files = 0;
|
||||
srv->autoindex_file_limit = file_limit;
|
||||
}
|
||||
if (file_limit <= 0) {
|
||||
return true;
|
||||
}
|
||||
cbm_discover_opts_t opts = {.mode = CBM_MODE_FULL, .ignore_file = NULL, .max_file_size = 0};
|
||||
int count = 0;
|
||||
if (cbm_discover_count_bounded(root_path, &opts, file_limit, &count) != 0) {
|
||||
if (srv) {
|
||||
srv->autoindex_block = MCP_AUTOINDEX_BLOCK_FILE_COUNT;
|
||||
}
|
||||
cbm_log_warn("autoindex.skip", "reason", "file_count_failed", "path",
|
||||
root_path ? root_path : "");
|
||||
return false;
|
||||
}
|
||||
if (count > file_limit) {
|
||||
if (srv) {
|
||||
srv->autoindex_block = MCP_AUTOINDEX_BLOCK_FILE_LIMIT;
|
||||
srv->autoindex_observed_files = count;
|
||||
}
|
||||
char count_buf[CBM_SZ_32];
|
||||
snprintf(count_buf, sizeof(count_buf), "%d", count);
|
||||
cbm_log_warn("autoindex.skip", "reason", "too_many_files", "files", count_buf, "limit",
|
||||
@@ -3905,6 +3927,33 @@ static void add_git_context_json(yyjson_mut_doc *doc, yyjson_mut_val *obj, const
|
||||
cbm_git_context_free(&ctx);
|
||||
}
|
||||
|
||||
/* Describe the exact indexing call sequence exposed by this server. This is
|
||||
* reached only after resolve_project_store exhausted permissible automatic
|
||||
* recovery: it joins local startup work, retries a failed thread launch with a
|
||||
* synchronous index, and attempts configured first-use indexing. Explicit
|
||||
* auto_index=false and the resource-protection limit must not be overridden.
|
||||
* The MCP profile and reveal state are the authorities; never tell a caller to
|
||||
* invoke a tool that its current tools/list hides. */
|
||||
static void mcp_index_recovery_action(cbm_mcp_server_t *srv, char *out, size_t out_size) {
|
||||
if (!out || out_size == 0) {
|
||||
return;
|
||||
}
|
||||
bool allowed = !srv || mcp_tool_allowed(srv->tool_profile, "index_repository");
|
||||
bool visible = allowed && (!srv || cbm_mcp_advanced_tool_visible(srv, "index_repository"));
|
||||
if (visible) {
|
||||
snprintf(out, out_size, "call index_repository with repo_path='/absolute/path/to/repo'.");
|
||||
} else if (allowed) {
|
||||
snprintf(out, out_size,
|
||||
"call _hidden_tools, refresh tools/list, then call index_repository with "
|
||||
"repo_path='/absolute/path/to/repo'.");
|
||||
} else {
|
||||
snprintf(out, out_size,
|
||||
"this MCP tool profile does not expose index_repository; run "
|
||||
"codebase-memory-mcp cli index_repository --repo-path "
|
||||
"'/absolute/path/to/repo'.");
|
||||
}
|
||||
}
|
||||
|
||||
/* Build a helpful error listing available projects. Caller must free() result. */
|
||||
static char *build_project_list_error_srv(cbm_mcp_server_t *srv, const char *reason) {
|
||||
char dir_path[1024];
|
||||
@@ -3916,9 +3965,46 @@ static char *build_project_list_error_srv(cbm_mcp_server_t *srv, const char *rea
|
||||
int total_count = collect_db_project_names(dir_path, projects, sizeof(projects),
|
||||
&listed_count, &projects_truncated);
|
||||
|
||||
char index_action[CBM_SZ_512];
|
||||
mcp_index_recovery_action(srv, index_action, sizeof(index_action));
|
||||
char recovery_hint[CBM_SZ_1K];
|
||||
switch (srv ? srv->autoindex_block : MCP_AUTOINDEX_BLOCK_NONE) {
|
||||
case MCP_AUTOINDEX_BLOCK_DISABLED:
|
||||
snprintf(recovery_hint, sizeof(recovery_hint),
|
||||
"Automatic indexing is disabled (auto_index=false). Set auto_index=true and "
|
||||
"retry, or %s",
|
||||
index_action);
|
||||
break;
|
||||
case MCP_AUTOINDEX_BLOCK_FILE_COUNT:
|
||||
snprintf(recovery_hint, sizeof(recovery_hint),
|
||||
"Automatic indexing could not count project files safely. Check project read "
|
||||
"permissions, then retry; %s",
|
||||
index_action);
|
||||
break;
|
||||
case MCP_AUTOINDEX_BLOCK_FILE_LIMIT:
|
||||
snprintf(recovery_hint, sizeof(recovery_hint),
|
||||
"Automatic indexing stopped after more than %d files exceeded "
|
||||
"auto_index_limit=%d. Check available memory before raising the limit and "
|
||||
"retrying; if the larger run is intentional, %s",
|
||||
srv->autoindex_observed_files, srv->autoindex_file_limit, index_action);
|
||||
break;
|
||||
case MCP_AUTOINDEX_BLOCK_NONE:
|
||||
default:
|
||||
snprintf(recovery_hint, sizeof(recovery_hint),
|
||||
"No published index is readable. Pass the repository path as project to use "
|
||||
"configured automatic indexing, or %s",
|
||||
index_action);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Optional: session_project and _context fields for richer error context */
|
||||
char session_frag[256] = "";
|
||||
char context_frag[512] = "";
|
||||
char context_frag[CBM_SZ_2K] = "";
|
||||
const char *context_hint =
|
||||
total_count == 0
|
||||
? recovery_hint
|
||||
: "The requested project has no readable published index. Use list_projects and "
|
||||
"pass the intended project explicitly.";
|
||||
if (srv && srv->session_project[0]) {
|
||||
snprintf(session_frag, sizeof(session_frag),
|
||||
",\"session_project\":\"%s\"", srv->session_project);
|
||||
@@ -3927,7 +4013,8 @@ static char *build_project_list_error_srv(cbm_mcp_server_t *srv, const char *rea
|
||||
if (ctx_enabled && !srv->context_injected) {
|
||||
snprintf(context_frag, sizeof(context_frag),
|
||||
",\"_context\":{\"status\":\"not_indexed\","
|
||||
"\"hint\":\"No project indexed yet. Pass project='/path/to/repo' to index.\"}");
|
||||
"\"hint\":\"%s\"}",
|
||||
context_hint);
|
||||
srv->context_injected = true; /* one-shot: suppress from future successful responses */
|
||||
}
|
||||
}
|
||||
@@ -3950,10 +4037,8 @@ static char *build_project_list_error_srv(cbm_mcp_server_t *srv, const char *rea
|
||||
}
|
||||
}
|
||||
} else {
|
||||
snprintf(buf, sizeof(buf),
|
||||
"{\"error\":\"%s\",\"hint\":\"No projects indexed yet. "
|
||||
"Call index_repository first.\"%s%s}",
|
||||
reason, session_frag, context_frag);
|
||||
snprintf(buf, sizeof(buf), "{\"error\":\"%s\",\"hint\":\"%s\"%s%s}", reason, recovery_hint,
|
||||
session_frag, context_frag);
|
||||
}
|
||||
return heap_strdup(buf);
|
||||
}
|
||||
@@ -15513,6 +15598,9 @@ static int cbm_mcp_reindex_stale_seconds(cbm_mcp_server_t *srv) {
|
||||
|
||||
/* Start auto-indexing if configured and project not yet indexed. */
|
||||
static void maybe_auto_index(cbm_mcp_server_t *srv) {
|
||||
srv->autoindex_block = MCP_AUTOINDEX_BLOCK_NONE;
|
||||
srv->autoindex_observed_files = 0;
|
||||
srv->autoindex_file_limit = cbm_mcp_auto_index_limit(srv);
|
||||
if (srv->session_root[0] == '\0') {
|
||||
return; /* no session root detected */
|
||||
}
|
||||
@@ -15563,6 +15651,7 @@ static void maybe_auto_index(cbm_mcp_server_t *srv) {
|
||||
bool auto_index = cbm_mcp_auto_index_enabled(srv);
|
||||
|
||||
if (!auto_index) {
|
||||
srv->autoindex_block = MCP_AUTOINDEX_BLOCK_DISABLED;
|
||||
cbm_log_info("autoindex.skip", "reason", "disabled", "hint",
|
||||
"export CBM_AUTO_INDEX=true OR codebase-memory-mcp config set auto_index true");
|
||||
return;
|
||||
@@ -15577,6 +15666,11 @@ static void maybe_auto_index(cbm_mcp_server_t *srv) {
|
||||
/* Launch auto-index in background */
|
||||
if (cbm_thread_create(&srv->autoindex_tid, 0, autoindex_thread, srv) == 0) {
|
||||
srv->autoindex_active = true;
|
||||
} else {
|
||||
/* Do not turn a transient thread-launch failure into a user task. The
|
||||
* first store-backed request runs the existing synchronous first-use
|
||||
* path before REQUIRE_STORE_EX can build an error. */
|
||||
cbm_log_warn("autoindex.skip", "reason", "thread_start_failed");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9570,6 +9570,136 @@ TEST(first_search_code_call_waits_for_startup_index_instead_of_reporting_not_ind
|
||||
PASS();
|
||||
}
|
||||
|
||||
static char *request_missing_index_with_mode(cbm_config_t *config, int request_id,
|
||||
bool reveal_hidden_tools) {
|
||||
cbm_mcp_server_t *srv = cbm_mcp_server_new(NULL);
|
||||
if (!srv) {
|
||||
return NULL;
|
||||
}
|
||||
cbm_mcp_server_set_config(srv, config);
|
||||
char *initialize = cbm_mcp_server_handle(
|
||||
srv, "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\",\"params\":{}}");
|
||||
if (!initialize) {
|
||||
cbm_mcp_server_free(srv);
|
||||
return NULL;
|
||||
}
|
||||
free(initialize);
|
||||
if (reveal_hidden_tools) {
|
||||
char *reveal = cbm_mcp_handle_tool(srv, "_hidden_tools", "{}");
|
||||
if (!reveal) {
|
||||
cbm_mcp_server_free(srv);
|
||||
return NULL;
|
||||
}
|
||||
free(reveal);
|
||||
}
|
||||
char request[CBM_SZ_1K];
|
||||
int written = snprintf(request, sizeof(request),
|
||||
"{\"jsonrpc\":\"2.0\",\"id\":%d,\"method\":\"tools/call\","
|
||||
"\"params\":{\"name\":\"search_graph\",\"arguments\":{"
|
||||
"\"name_pattern\":\"blocked_index_target\"}}}",
|
||||
request_id);
|
||||
char *response = written > 0 && (size_t)written < sizeof(request)
|
||||
? cbm_mcp_server_handle(srv, request)
|
||||
: NULL;
|
||||
cbm_mcp_server_free(srv);
|
||||
return response;
|
||||
}
|
||||
|
||||
static bool response_has_structured_content(const char *response) {
|
||||
yyjson_doc *doc = response ? yyjson_read(response, strlen(response), 0) : NULL;
|
||||
yyjson_val *root = doc ? yyjson_doc_get_root(doc) : NULL;
|
||||
yyjson_val *result = root ? yyjson_obj_get(root, "result") : NULL;
|
||||
bool found = result && yyjson_is_obj(yyjson_obj_get(result, "structuredContent"));
|
||||
yyjson_doc_free(doc);
|
||||
return found;
|
||||
}
|
||||
|
||||
TEST(first_search_reports_automatic_index_block_reason) {
|
||||
char repo[CBM_SZ_256];
|
||||
char cache[CBM_SZ_256];
|
||||
snprintf(repo, sizeof(repo), "/tmp/cbm-index-block-repo-XXXXXX");
|
||||
snprintf(cache, sizeof(cache), "/tmp/cbm-index-block-cache-XXXXXX");
|
||||
ASSERT_NOT_NULL(cbm_mkdtemp(repo));
|
||||
ASSERT_NOT_NULL(cbm_mkdtemp(cache));
|
||||
|
||||
const char *saved_cache = getenv("CBM_CACHE_DIR");
|
||||
char *saved_cache_copy = saved_cache ? cbm_strdup(saved_cache) : NULL;
|
||||
cbm_setenv("CBM_CACHE_DIR", cache, 1);
|
||||
|
||||
char source_path[CBM_SZ_512];
|
||||
snprintf(source_path, sizeof(source_path), "%s/blocked.py", repo);
|
||||
FILE *source = fopen(source_path, "w");
|
||||
ASSERT_NOT_NULL(source);
|
||||
fputs("def blocked_index_target():\n return 42\n", source);
|
||||
fclose(source);
|
||||
char second_source_path[CBM_SZ_512];
|
||||
snprintf(second_source_path, sizeof(second_source_path), "%s/also_blocked.py", repo);
|
||||
source = fopen(second_source_path, "w");
|
||||
ASSERT_NOT_NULL(source);
|
||||
fputs("def second_blocked_target():\n return 43\n", source);
|
||||
fclose(source);
|
||||
|
||||
char old_cwd[CBM_SZ_1K];
|
||||
ASSERT_NOT_NULL(cbm_getcwd(old_cwd, sizeof(old_cwd)));
|
||||
ASSERT_EQ(cbm_chdir(repo), 0);
|
||||
|
||||
cbm_config_t *config = cbm_config_open(cache);
|
||||
ASSERT_NOT_NULL(config);
|
||||
ASSERT_EQ(cbm_config_set(config, CBM_CONFIG_AUTO_INDEX, "false"), 0);
|
||||
|
||||
char *response = request_missing_index_with_mode(config, 65, false);
|
||||
ASSERT_NOT_NULL(response);
|
||||
ASSERT_TRUE(response_has_structured_content(response));
|
||||
ASSERT_NOT_NULL(strstr(response, "auto_index=false"));
|
||||
ASSERT_NOT_NULL(strstr(response, "_hidden_tools"));
|
||||
ASSERT_NOT_NULL(strstr(response, "tools/list"));
|
||||
ASSERT_NOT_NULL(strstr(response, "index_repository"));
|
||||
ASSERT_NOT_NULL(strstr(response, "repo_path"));
|
||||
ASSERT_NULL(strstr(response, "\"status\":\"auto_indexing\""));
|
||||
free(response);
|
||||
|
||||
ASSERT_EQ(cbm_config_set(config, CBM_CONFIG_AUTO_INDEX, "true"), 0);
|
||||
ASSERT_EQ(cbm_config_set(config, CBM_CONFIG_AUTO_INDEX_LIMIT, "1"), 0);
|
||||
response = request_missing_index_with_mode(config, 67, false);
|
||||
ASSERT_NOT_NULL(response);
|
||||
ASSERT_TRUE(response_has_structured_content(response));
|
||||
ASSERT_NOT_NULL(strstr(response, "auto_index_limit"));
|
||||
ASSERT_NOT_NULL(strstr(response, "_hidden_tools"));
|
||||
ASSERT_NOT_NULL(strstr(response, "tools/list"));
|
||||
ASSERT_NOT_NULL(strstr(response, "index_repository"));
|
||||
ASSERT_NOT_NULL(strstr(response, "repo_path"));
|
||||
ASSERT_NULL(strstr(response, "\"status\":\"auto_indexing\""));
|
||||
free(response);
|
||||
|
||||
ASSERT_EQ(cbm_config_set(config, CBM_CONFIG_AUTO_INDEX, "false"), 0);
|
||||
ASSERT_EQ(cbm_config_set(config, CBM_CONFIG_TOOL_MODE, CBM_CONFIG_TOOL_MODE_CLASSIC), 0);
|
||||
response = request_missing_index_with_mode(config, 69, false);
|
||||
ASSERT_NOT_NULL(response);
|
||||
ASSERT_TRUE(response_has_structured_content(response));
|
||||
ASSERT_NOT_NULL(strstr(response, "call index_repository"));
|
||||
ASSERT_NOT_NULL(strstr(response, "repo_path"));
|
||||
ASSERT_NULL(strstr(response, "_hidden_tools"));
|
||||
free(response);
|
||||
|
||||
ASSERT_EQ(cbm_config_set(config, CBM_CONFIG_TOOL_MODE, CBM_CONFIG_TOOL_MODE_STREAMLINED), 0);
|
||||
response = request_missing_index_with_mode(config, 71, true);
|
||||
ASSERT_NOT_NULL(response);
|
||||
ASSERT_TRUE(response_has_structured_content(response));
|
||||
ASSERT_NOT_NULL(strstr(response, "call index_repository"));
|
||||
ASSERT_NOT_NULL(strstr(response, "repo_path"));
|
||||
ASSERT_NULL(strstr(response, "_hidden_tools"));
|
||||
ASSERT_NULL(strstr(response, "refresh tools/list"));
|
||||
free(response);
|
||||
|
||||
cbm_config_close(config);
|
||||
ASSERT_EQ(cbm_chdir(old_cwd), 0);
|
||||
restore_cache_dir(saved_cache_copy);
|
||||
free(saved_cache_copy);
|
||||
th_rmtree(repo);
|
||||
th_rmtree(cache);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════
|
||||
* POLL/GETLINE FILE* BUFFERING FIX
|
||||
* ══════════════════════════════════════════════════════════════════ */
|
||||
@@ -12618,6 +12748,7 @@ SUITE(mcp) {
|
||||
RUN_TEST(server_handle_unknown_tool_preserves_string_id);
|
||||
RUN_TEST(first_graph_call_waits_for_startup_index_and_returns_ready_context);
|
||||
RUN_TEST(first_search_code_call_waits_for_startup_index_instead_of_reporting_not_indexed);
|
||||
RUN_TEST(first_search_reports_automatic_index_block_reason);
|
||||
|
||||
/* Tool handlers */
|
||||
RUN_TEST(tool_list_projects_empty);
|
||||
|
||||
Reference in New Issue
Block a user