| 11431 | } |
| 11432 | |
| 11433 | char *cbm_mcp_handle_tool(cbm_mcp_server_t *srv, const char *tool_name, const char *args_json) { |
| 11434 | /* Phase marks bracket the WHOLE request with no unlabelled gap, so growth |
| 11435 | * cannot hide between them (CBM_MEM_PHASES=1; see foundation/mem.h). The |
| 11436 | * "idle" label owns everything outside a request, which is what makes a |
| 11437 | * request-path retainer distinguishable from background growth. */ |
| 11438 | cbm_mem_phase_mark("request.scope_begin"); |
| 11439 | bool request_scope = !srv || cbm_mcp_server_request_scope_begin(srv); |
| 11440 | if (!request_scope) { |
| 11441 | release_request_store(srv); |
| 11442 | cbm_mem_phase_mark("idle"); |
| 11443 | return cbm_mcp_text_result("request cancellation scope unavailable", true); |
| 11444 | } |
| 11445 | cbm_mem_phase_mark("request.dispatch_tool"); |
| 11446 | char *result = dispatch_tool(srv, tool_name, args_json); |
| 11447 | cbm_mem_phase_mark("request.scope_end"); |
| 11448 | if (srv) { |
| 11449 | cbm_mcp_server_request_scope_end(srv); |
| 11450 | } |
| 11451 | cbm_mem_phase_mark("request.release_store"); |
| 11452 | release_request_store(srv); |
| 11453 | cbm_mem_phase_mark("idle"); |
| 11454 | /* One census per completed request, so growth can be attributed to a POOL |
| 11455 | * rather than inferred from a process total (#581). Emitted after the |
| 11456 | * request store is released, which is the point where a well-behaved |
| 11457 | * request has given everything back. */ |
| 11458 | cbm_mem_census_log("mcp.request"); |
| 11459 | return result; |
| 11460 | } |
| 11461 | |
| 11462 | /* ── Session detection + auto-index ────────────────────────────── */ |
| 11463 | |