Skip to content

Commit 99723b3

Browse files
DeusDatatest
authored andcommitted
Improve error handling, diagnostics, and cancellation across MCP tools and CLI
- Tool handlers: set isError:true on actual failures (delete_project, manage_adr, detect_changes), include errno context in system errors, add structured hints for empty results (search_graph, query_graph, trace_path, list_projects, index_status) - verify_project_indexed: include available projects list in error response - CLI: extract text from MCP envelope for human-readable output, errors to stderr, exit code 1 on tool errors, --json flag for raw output - Cancellation: handle notifications/cancelled, track active pipeline in server struct, signal handler cancels in-progress pipeline and releases lock, watcher checks g_shutdown before starting new indexing
1 parent f71fded commit 99723b3

3 files changed

Lines changed: 230 additions & 48 deletions

File tree

src/main.c

Lines changed: 78 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ enum {
4040
#include "ui/config.h"
4141
#include "ui/http_server.h"
4242
#include "ui/embedded_assets.h"
43+
#include <yyjson/yyjson.h>
4344

4445
#include <stdio.h>
4546
#include <stdlib.h>
@@ -61,6 +62,17 @@ static atomic_int g_shutdown = 0;
6162
static void signal_handler(int sig) {
6263
(void)sig;
6364
atomic_store(&g_shutdown, 1);
65+
66+
/* Cancel any in-progress pipeline (async-signal-safe: only does atomic_store) */
67+
if (g_server) {
68+
cbm_pipeline_t *p = cbm_mcp_server_active_pipeline(g_server);
69+
if (p) {
70+
cbm_pipeline_cancel(p);
71+
}
72+
}
73+
/* Release pipeline lock to prevent stale lock on restart */
74+
cbm_pipeline_unlock();
75+
6476
if (g_watcher) {
6577
cbm_watcher_stop(g_watcher);
6678
}
@@ -94,6 +106,11 @@ static void *http_thread(void *arg) {
94106
static int watcher_index_fn(const char *project_name, const char *root_path, void *user_data) {
95107
(void)user_data;
96108

109+
/* Skip indexing if shutdown is in progress */
110+
if (atomic_load(&g_shutdown)) {
111+
return 0;
112+
}
113+
97114
/* Non-blocking: skip if another pipeline is already running.
98115
* Watcher will retry on next poll cycle (5-60s). */
99116
if (!cbm_pipeline_try_lock()) {
@@ -117,29 +134,65 @@ static int watcher_index_fn(const char *project_name, const char *root_path, voi
117134

118135
/* ── CLI mode ───────────────────────────────────────────────────── */
119136

137+
#define CLI_USAGE "Usage: codebase-memory-mcp cli [--progress] [--json] <tool_name> [json_args]\n"
138+
139+
/* Extract text content from MCP tool result envelope and print it.
140+
* MCP results: {"content":[{"type":"text","text":"..."}],"isError":...}
141+
* Returns 1 if the result was an error, 0 otherwise. */
142+
static int cli_print_mcp_result(const char *result) {
143+
yyjson_doc *doc = yyjson_read(result, strlen(result), 0);
144+
if (!doc) {
145+
printf("%s\n", result);
146+
return 0;
147+
}
148+
149+
yyjson_val *root = yyjson_doc_get_root(doc);
150+
yyjson_val *err_val = yyjson_obj_get(root, "isError");
151+
bool is_error = err_val && yyjson_get_bool(err_val);
152+
153+
const char *text = NULL;
154+
yyjson_val *content = yyjson_obj_get(root, "content");
155+
if (yyjson_is_arr(content) && yyjson_arr_size(content) > 0) {
156+
yyjson_val *tv = yyjson_obj_get(yyjson_arr_get_first(content), "text");
157+
text = tv ? yyjson_get_str(tv) : NULL;
158+
}
159+
160+
if (text) {
161+
(void)fprintf(is_error ? stderr : stdout, "%s\n", text);
162+
} else {
163+
printf("%s\n", result);
164+
}
165+
166+
yyjson_doc_free(doc);
167+
return is_error ? SKIP_ONE : 0;
168+
}
169+
170+
/* Strip a flag from argv, returning true if found. */
171+
static bool cli_strip_flag(int *argc, char **argv, const char *flag) {
172+
for (int i = 0; i < *argc; i++) {
173+
if (strcmp(argv[i], flag) != 0) {
174+
continue;
175+
}
176+
for (int j = i; j < *argc - SKIP_ONE; j++) {
177+
argv[j] = argv[j + SKIP_ONE];
178+
}
179+
(*argc)--;
180+
return true;
181+
}
182+
return false;
183+
}
184+
120185
static int run_cli(int argc, char **argv) {
121186
if (argc < MAIN_MIN_ARGC) {
122-
(void)fprintf(stderr,
123-
"Usage: codebase-memory-mcp cli [--progress] <tool_name> [json_args]\n");
187+
(void)fprintf(stderr, CLI_USAGE);
124188
return SKIP_ONE;
125189
}
126190

127-
/* Strip --progress flag from argv. */
128-
bool progress = false;
129-
for (int i = 0; i < argc; i++) {
130-
if (strcmp(argv[i], "--progress") == 0) {
131-
progress = true;
132-
for (int j = i; j < argc - SKIP_ONE; j++) {
133-
argv[j] = argv[j + SKIP_ONE];
134-
}
135-
argc--;
136-
break;
137-
}
138-
}
191+
bool progress = cli_strip_flag(&argc, argv, "--progress");
192+
bool raw_json = cli_strip_flag(&argc, argv, "--json");
139193

140194
if (argc < MAIN_MIN_ARGC) {
141-
(void)fprintf(stderr,
142-
"Usage: codebase-memory-mcp cli [--progress] <tool_name> [json_args]\n");
195+
(void)fprintf(stderr, CLI_USAGE);
143196
return SKIP_ONE;
144197
}
145198

@@ -152,24 +205,30 @@ static int run_cli(int argc, char **argv) {
152205

153206
cbm_mcp_server_t *srv = cbm_mcp_server_new(NULL);
154207
if (!srv) {
155-
(void)fprintf(stderr, "Failed to create server\n");
208+
(void)fprintf(stderr, "error: failed to create server\n");
156209
if (progress) {
157210
cbm_progress_sink_fini();
158211
}
159212
return SKIP_ONE;
160213
}
161214

162215
char *result = cbm_mcp_handle_tool(srv, tool_name, args_json);
216+
int exit_code = 0;
217+
163218
if (result) {
164-
printf("%s\n", result);
219+
if (raw_json) {
220+
printf("%s\n", result);
221+
} else {
222+
exit_code = cli_print_mcp_result(result);
223+
}
165224
free(result);
166225
}
167226

168227
cbm_mcp_server_free(srv);
169228
if (progress) {
170229
cbm_progress_sink_fini();
171230
}
172-
return 0;
231+
return exit_code;
173232
}
174233

175234
/* ── Help ───────────────────────────────────────────────────────── */

0 commit comments

Comments
 (0)