summaryrefslogtreecommitdiff
path: root/src/mcp/tools/search_messages.c
blob: 166c167ba406e213accadff91f6e2906864a8c8f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
#include "mcp/tools/common/registry.h"

#include "mcp/tools/common/common.h"
#include "mcp/tools/common/context.h"

#include <stdint.h>
#include <stdlib.h>
#include <string.h>

#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 = 0;
  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] && session
        && mcpc_tool_get_tpropval_i32(tool, (const char8_t *)"limit", &requested_limit) == MCPC_EC_0
        && 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[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)
         && tiny_mcp_tool_add_property(tool, "session_id", "Session ID; empty for all", MCPC_U8STR)
         && tiny_mcp_tool_add_property(tool, "limit", "Result limit from 1 to 100", MCPC_I32)
         && mcpc_server_add_tool(server, tool) == MCPC_EC_0;
}