Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -669,12 +669,39 @@ JSON arguments can also be piped on stdin, for tools that take arguments. A tool

| Tool | Description |
|------|-------------|
| `index_repository` | Index a repository into the graph. Auto-sync keeps it fresh after that. |
| `index_repository` | Index a repository into the graph. Auto-sync keeps it fresh after that. Waits for the whole index by default; pass `async: true` to start it in the daemon and return at once, then poll with `status: true` (see below). |
| `list_projects` | List all indexed projects with node/edge counts. |
| `delete_project` | Remove a project and all its graph data. |
| `index_status` | Check indexing status of a project. |
| `check_index_coverage` | Check whether exact paths or a scope are indexed and fresh. A clean result means no recorded gap, not proof of completeness. |

**Long indexes and client call deadlines.** A synchronous `index_repository` on a large
repository can take longer than an MCP client allows one tool call (some IDE clients give up
after a fixed deadline). When a client cancels or disconnects, the daemon cancels an index that
nobody else is waiting for, so retrying the same blocking call never finishes. Use the async
mode instead:

1. `index_repository(repo_path="/abs/path", async: true)` starts the index in the daemon (or
joins the one already running for that project) and returns immediately with
`state` (`queued`/`running`). The job keeps running even if the client cancels, times out or
disconnects; only stopping the daemon ends it.
2. `index_repository(repo_path="/abs/path", status: true)` reports `state`
(`queued`, `running`, `cancelling`, `succeeded`, `failed`, `cancelled`), `started_at`,
`finished_at` and an `error` summary. Poll it until the state is `succeeded`, `failed` or
`cancelled`. Pass the same `repo_path` (and `name`, if the index call used one).

`async` and `status` are exclusive; `async` does not apply to `cross-repo-intelligence`. Both
need the daemon-backed server (the default `codebase-memory-mcp` entry point). A temporary
daemon (started on demand rather than by `codebase-memory-mcp daemon start`) stops, and
cancels its jobs, when its last client disconnects. A connected MCP session keeps it alive, so
async from your editor works. A one-shot `codebase-memory-mcp cli index_repository --async`
that is the daemon's only client is refused with a clear error, because the job would die the
moment the command exits: run `codebase-memory-mcp daemon start` first, keep an MCP session
open, or call without `--async`. `status` works everywhere. When a synchronous call was cut
short, the next
`index_repository` or `status` call for that project carries a `notice` suggesting the async
mode. `index_status` keeps describing the published graph and its freshness.

### Querying

| Tool | Description |
Expand Down Expand Up @@ -799,6 +826,7 @@ SQLite databases stored at `~/.cache/codebase-memory-mcp/`. Persists across rest
|---------|-----|
| `/mcp` doesn't show the server | Check `.mcp.json` path is absolute. Restart agent. Test: `echo '{}' \| /path/to/binary` should output JSON. |
| `index_repository` fails | Pass absolute path: `index_repository(repo_path="/absolute/path")` |
| `index_repository` times out in the client | Start it with `async: true`, then poll with `status: true` (see [Indexing](#indexing)). |
| `trace_path` returns 0 results | Use `search_graph(name_pattern=".*PartialName.*")` first to find the exact name. |
| Queries return wrong project results | Add `project="name"` parameter. Use `list_projects` to see names. |
| Binary not found after install | Add to PATH: `export PATH="$HOME/.local/bin:$PATH"` |
Expand Down
445 changes: 429 additions & 16 deletions src/daemon/application.c

Large diffs are not rendered by default.

20 changes: 19 additions & 1 deletion src/daemon/frontend.c
Original file line number Diff line number Diff line change
Expand Up @@ -382,8 +382,26 @@ static bool frontend_write_response(FILE *out, const uint8_t *response, uint32_t
return fflush(out) == 0 && written;
}

const char *cbm_daemon_frontend_cancelled_error_message(const char *request_message) {
static const char plain[] = "Request cancelled";
static const char index_call[] = "Request cancelled. " CBM_MCP_INDEX_ASYNC_HINT;
cbm_jsonrpc_request_t request = {0};
if (!request_message || cbm_jsonrpc_parse(request_message, &request) != 0) {
return plain;
}
char *tool = request.method && strcmp(request.method, "tools/call") == 0 && request.params_raw
? cbm_mcp_get_string_arg(request.params_raw, "name")
: NULL;
bool index_repository = tool && strcmp(tool, "index_repository") == 0;
safe_free(tool);
cbm_jsonrpc_request_free(&request);
return index_repository ? index_call : plain;
}

static bool frontend_write_cancelled_response(FILE *out, const frontend_item_t *item) {
static const char cancelled_error[] = "{\"code\":-32800,\"message\":\"Request cancelled\"}";
char cancelled_error[512];
(void)snprintf(cancelled_error, sizeof(cancelled_error), "{\"code\":-32800,\"message\":\"%s\"}",
cbm_daemon_frontend_cancelled_error_message(item->message));
cbm_jsonrpc_response_t response = {
.id = item->id,
.id_str = item->id_str,
Expand Down
6 changes: 6 additions & 0 deletions src/daemon/frontend.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ bool cbm_daemon_frontend_is_cancellation_notification(const char *message);
bool cbm_daemon_frontend_cancellation_matches_request(const char *message, int64_t active_id,
const char *active_id_str);

/* The JSON-RPC error message answered for a cancelled request (#2144). A
* cancelled index_repository call is usually a client deadline on a long
* index, so its reply names the async alternative; every other request keeps
* the plain message. Returns a static string free of JSON metacharacters. */
const char *cbm_daemon_frontend_cancelled_error_message(const char *request_message);

/* Start a temporary observer for a one-shot local CLI command or physical
* supervised worker. manager and cancel_context are borrowed until stop. On
* maintenance intent the observer invokes cancel once, permits a fixed bounded
Expand Down
Loading
Loading