| 2410 | } |
| 2411 | |
| 2412 | static cbm_daemon_runtime_application_status_t application_mcp_request( |
| 2413 | cbm_daemon_application_session_t *session, const uint8_t *request, uint32_t request_length, |
| 2414 | uint8_t **response_out, uint32_t *response_length_out) { |
| 2415 | if (!session->context_set || request_length <= 1) { |
| 2416 | return CBM_DAEMON_RUNTIME_APPLICATION_REJECTED; |
| 2417 | } |
| 2418 | char *message = application_text_copy(request + 1, request_length - 1); |
| 2419 | if (!message) { |
| 2420 | return CBM_DAEMON_RUNTIME_APPLICATION_REJECTED; |
| 2421 | } |
| 2422 | cbm_jsonrpc_request_t parsed = {0}; |
| 2423 | bool parsed_ok = cbm_jsonrpc_parse(message, &parsed) == 0; |
| 2424 | bool initialize_request = |
| 2425 | parsed_ok && parsed.has_id && parsed.method && strcmp(parsed.method, "initialize") == 0; |
| 2426 | bool tool_request = |
| 2427 | parsed_ok && parsed.has_id && parsed.method && strcmp(parsed.method, "tools/call") == 0; |
| 2428 | char *response = cbm_mcp_server_handle(session->mcp, message); |
| 2429 | free(message); |
| 2430 | if (initialize_request && application_jsonrpc_success(response)) { |
| 2431 | cbm_mutex_lock(&session->application->mutex); |
| 2432 | session->pending_background_initialize = true; |
| 2433 | cbm_mutex_unlock(&session->application->mutex); |
| 2434 | } else if (tool_request && response) { |
| 2435 | application_update_notice_inject(session, &response); |
| 2436 | } |
| 2437 | if (response) { |
| 2438 | size_t response_length = strlen(response); |
| 2439 | if (response_length > UINT32_MAX) { |
| 2440 | if (parsed_ok) { |
| 2441 | cbm_jsonrpc_request_free(&parsed); |
| 2442 | } |
| 2443 | free(response); |
| 2444 | return CBM_DAEMON_RUNTIME_APPLICATION_HANDLER_ERROR; |
| 2445 | } |
| 2446 | /* A reply larger than one frame has to become a JSON-RPC error HERE, |
| 2447 | * while the request id is still in hand. Handing it to the transport |
| 2448 | * instead is indistinguishable from a dead socket at the frontend |
| 2449 | * worker, which responds by _Exit()ing the process — right for a broken |
| 2450 | * transport, a fatal over-reaction to a large answer. Under an MCP host |
| 2451 | * that does not respawn the server, that took every tool on it out for |
| 2452 | * the rest of the session, leaving exit=1 and an empty stderr to debug |
| 2453 | * from (#1375). */ |
| 2454 | response = application_framable_response(response, parsed_ok ? &parsed : NULL); |
| 2455 | if (!response) { |
| 2456 | if (parsed_ok) { |
| 2457 | cbm_jsonrpc_request_free(&parsed); |
| 2458 | } |
| 2459 | return CBM_DAEMON_RUNTIME_APPLICATION_HANDLER_ERROR; |
| 2460 | } |
| 2461 | response_length = strlen(response); |
| 2462 | *response_out = (uint8_t *)response; |
| 2463 | *response_length_out = (uint32_t)response_length; |
| 2464 | } |
| 2465 | if (parsed_ok) { |
| 2466 | cbm_jsonrpc_request_free(&parsed); |
| 2467 | } |
| 2468 | application_refresh_watch(session); |
| 2469 | return CBM_DAEMON_RUNTIME_APPLICATION_OK; |
no test coverage detected