#include "mcp/tools/common/registry.h" #include "mcp/tools/common/common.h" #include "mcp/tools/common/context.h" #include #include #include #include "base/allocator.h" typedef struct result_item { const tiny_store_entry_t *entry; double score; } result_item_t; static int compare_results(const void *left, const void *right) { const result_item_t *a = left; const result_item_t *b = right; if (a->score > b->score) { return -1; } if (a->score < b->score) { return 1; } if (a->entry->message.timestamp_ms > b->entry->message.timestamp_ms) { return -1; } if (a->entry->message.timestamp_ms < b->entry->message.timestamp_ms) { return 1; } return strcmp(a->entry->message.message_id, b->entry->message.message_id); } static void call_search_messages(const mcpc_tool_t *tool, mcpc_ucbr_t *result) { tiny_mcp_tools_context_t *context = tiny_mcp_tools_context_current(); char *query = tiny_mcp_tool_get_string(tool, "query", TINY_QUERY_MAX); char *session = tiny_mcp_tool_get_string(tool, "session_id", TINY_SESSION_ID_MAX); int32_t requested_limit = 20; mcpc_errcode_t limit_status = mcpc_tool_get_tpropval_i32(tool, (const char8_t *)"limit", &requested_limit); size_t limit, index, count = 0, candidate_count = 0, *candidates = NULL; result_item_t *items = NULL; tiny_json_buffer_t json; bool valid = context && query && query[0] && (limit_status == MCPC_EC_0 || limit_status == MCPC_EC_ANYDATA_NOTFOUND) && requested_limit >= 1 && requested_limit <= 100; if (!valid) { tiny_mcp_tool_add_error(result, "VALIDATION_ERROR", "query or limit is invalid"); } else { limit = (size_t)requested_limit; if (!context->search_index) { context->search_index = tiny_mcp_tools_build_search_index(context->store); } if (!context->search_index || !tiny_search_index_candidates( context->search_index, query, &candidates, &candidate_count)) { tiny_mcp_tool_add_error(result, "INTERNAL_ERROR", "search index allocation failed"); } else { items = malloc((candidate_count + 1) * sizeof(*items)); if (!items) { tiny_mcp_tool_add_error(result, "INTERNAL_ERROR", "out of memory"); } else { for (index = 0; index < candidate_count; ++index) { const tiny_store_entry_t *entry = tiny_store_entry(context->store, candidates[index]); double score; if (!entry || entry->deleted || (session && session[0] && strcmp(entry->message.session_id, session) != 0)) { continue; } score = tiny_search_score(query, &entry->message); if (score > 0) { items[count].entry = entry; items[count++].score = score; } } qsort(items, count, sizeof(*items), compare_results); if (count > limit) { count = limit; } tiny_json_init(&json); tiny_json_raw(&json, "{\"results\":["); for (index = 0; index < count; ++index) { if (index) { tiny_json_raw(&json, ","); } tiny_mcp_tool_append_message(&json, &items[index].entry->message); } tiny_json_raw(&json, "]}"); tiny_mcp_tool_add_json(result, &json); tiny_json_free(&json); } } } free(items); free(candidates); free(query); free(session); } bool tiny_mcp_register_search_messages(mcpc_server_t *server) { mcpc_tool_t *tool = tiny_mcp_tool_new("search_messages", "Search messages", call_search_messages); return tool && tiny_mcp_tool_add_property(tool, "query", "Search text", MCPC_U8STR, true) && tiny_mcp_tool_add_property( tool, "session_id", "Optional session ID; omitted searches all", MCPC_U8STR, false) && tiny_mcp_tool_add_property( tool, "limit", "Optional result limit from 1 to 100; defaults to 20", MCPC_I32, false) && mcpc_server_add_tool(server, tool) == MCPC_EC_0; }