master
c 201 lines 6.52 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "mcp-sse.h"
4
5 #include "web/server/web_client.h"
6 #include "web/mcp/mcp-jsonrpc.h"
7 #include "web/mcp/mcp.h"
8 #include "mcp-http-common.h"
9
10 #include "web/api/mcp_auth.h"
11
12 #include "libnetdata/libnetdata.h"
13 #include "libnetdata/http/http_defs.h"
14 #include "libnetdata/http/content_type.h"
15
16 #include <json-c/json.h>
17
18 static void mcp_sse_add_common_headers(struct web_client *w) {
19 if (!w)
20 return;
21
22 buffer_flush(w->response.header);
23 buffer_strcat(w->response.header, "Cache-Control: no-cache\r\n");
24 buffer_strcat(w->response.header, "Connection: keep-alive\r\n");
25 }
26
27 #ifdef NETDATA_MCP_DEV_PREVIEW_API_KEY
28 static void mcp_sse_apply_api_key(struct web_client *w) {
29 if (web_client_has_mcp_preview_key(w)) {
30 web_client_set_permissions(w, HTTP_ACCESS_ALL, HTTP_USER_ROLE_ADMIN, USER_AUTH_METHOD_GOD);
31 return;
32 }
33
34 char api_key_buffer[MCP_DEV_PREVIEW_API_KEY_LENGTH + 1];
35 if (mcp_http_extract_api_key(w, api_key_buffer, sizeof(api_key_buffer)) &&
36 mcp_api_key_verify(api_key_buffer, false)) { // silent=false for MCP requests
37 web_client_set_permissions(w, HTTP_ACCESS_ALL, HTTP_USER_ROLE_ADMIN, USER_AUTH_METHOD_GOD);
38 }
39 }
40 #endif
41
42 static void mcp_sse_append_event(BUFFER *out, const char *event, const char *data) {
43 if (!out || !event)
44 return;
45
46 buffer_strcat(out, "event: ");
47 buffer_strcat(out, event);
48 buffer_strcat(out, "\n");
49
50 if (data && *data) {
51 buffer_strcat(out, "data: ");
52 buffer_strcat(out, data);
53 buffer_strcat(out, "\n");
54 }
55
56 buffer_strcat(out, "\n");
57 }
58
59 static void mcp_sse_append_buffer_event(BUFFER *out, const char *event, BUFFER *payload) {
60 if (!out || !event || !payload)
61 return;
62
63 buffer_strcat(out, "event: ");
64 buffer_strcat(out, event);
65 buffer_strcat(out, "\n");
66
67 buffer_strcat(out, "data: ");
68 buffer_fast_strcat(out, buffer_tostring(payload), buffer_strlen(payload));
69 buffer_strcat(out, "\n\n");
70 }
71
72 int mcp_sse_serialize_response(struct web_client *w, MCP_CLIENT *mcpc, struct json_object *root) {
73 if (!w || !mcpc || !root)
74 return HTTP_RESP_INTERNAL_SERVER_ERROR;
75
76 BUFFER **responses = NULL;
77 size_t responses_used = 0;
78 size_t responses_size = 0;
79
80 if (json_object_is_type(root, json_type_array)) {
81 size_t len = json_object_array_length(root);
82 for (size_t i = 0; i < len; i++) {
83 struct json_object *req_item = json_object_array_get_idx(root, i);
84 BUFFER *resp_item = mcp_jsonrpc_process_single_request(mcpc, req_item, NULL);
85 if (!resp_item)
86 continue;
87
88 if (responses_used == responses_size) {
89 size_t new_size = responses_size ? responses_size * 2 : 4;
90 BUFFER **tmp = reallocz(responses, new_size * sizeof(*tmp));
91 if (!tmp) {
92 buffer_free(resp_item);
93 continue;
94 }
95 responses = tmp;
96 responses_size = new_size;
97 }
98 responses[responses_used++] = resp_item;
99 }
100 } else {
101 BUFFER *resp = mcp_jsonrpc_process_single_request(mcpc, root, NULL);
102 if (resp) {
103 responses = reallocz(responses, sizeof(*responses));
104 if (responses)
105 responses[responses_used++] = resp;
106 else
107 buffer_free(resp);
108 }
109 }
110
111 buffer_flush(w->response.data);
112 w->response.data->content_type = CT_TEXT_EVENT_STREAM;
113 mcp_http_disable_compression(w);
114 mcp_sse_add_common_headers(w);
115
116 for (size_t i = 0; i < responses_used; i++) {
117 if (!responses[i])
118 continue;
119 mcp_sse_append_buffer_event(w->response.data, "message", responses[i]);
120 buffer_free(responses[i]);
121 }
122 freez(responses);
123
124 mcp_sse_append_event(w->response.data, "complete", "{}");
125
126 w->response.code = HTTP_RESP_OK;
127 return w->response.code;
128 }
129
130 int mcp_sse_handle_request(struct rrdhost *host __maybe_unused, struct web_client *w) {
131 if (!w)
132 return HTTP_RESP_INTERNAL_SERVER_ERROR;
133
134 if (w->mode != HTTP_REQUEST_MODE_GET && w->mode != HTTP_REQUEST_MODE_POST) {
135 buffer_flush(w->response.data);
136 buffer_strcat(w->response.data, "Unsupported HTTP method for /sse\n");
137 w->response.data->content_type = CT_TEXT_PLAIN;
138 w->response.code = HTTP_RESP_METHOD_NOT_ALLOWED;
139 return w->response.code;
140 }
141
142 #ifdef NETDATA_MCP_DEV_PREVIEW_API_KEY
143 mcp_sse_apply_api_key(w);
144 #endif
145
146 size_t body_len = 0;
147 const char *body = NULL;
148 if (w->payload)
149 body = buffer_tostring(w->payload);
150 if (body)
151 body_len = buffer_strlen(w->payload);
152
153 if (!body || !body_len) {
154 buffer_flush(w->response.data);
155 w->response.data->content_type = CT_TEXT_EVENT_STREAM;
156 mcp_http_disable_compression(w);
157 mcp_sse_add_common_headers(w);
158 mcp_sse_append_event(w->response.data, "error", "Empty request body");
159 w->response.code = HTTP_RESP_BAD_REQUEST;
160 return w->response.code;
161 }
162
163 enum json_tokener_error jerr = json_tokener_success;
164 struct json_object *root = json_tokener_parse_verbose(body, &jerr);
165 if (!root || jerr != json_tokener_success) {
166 BUFFER *payload = mcp_jsonrpc_build_error_payload(NULL, -32700, json_tokener_error_desc(jerr), NULL, 0);
167 buffer_flush(w->response.data);
168 w->response.data->content_type = CT_TEXT_EVENT_STREAM;
169 mcp_http_disable_compression(w);
170 mcp_sse_add_common_headers(w);
171 if (payload) {
172 mcp_sse_append_buffer_event(w->response.data, "error", payload);
173 buffer_free(payload);
174 } else {
175 mcp_sse_append_event(w->response.data, "error", json_tokener_error_desc(jerr));
176 }
177 w->response.code = HTTP_RESP_BAD_REQUEST;
178 if (root)
179 json_object_put(root);
180 return w->response.code;
181 }
182
183 MCP_CLIENT *mcpc = mcp_create_client(MCP_TRANSPORT_SSE, w);
184 if (!mcpc) {
185 json_object_put(root);
186 buffer_flush(w->response.data);
187 w->response.data->content_type = CT_TEXT_EVENT_STREAM;
188 mcp_http_disable_compression(w);
189 mcp_sse_add_common_headers(w);
190 mcp_sse_append_event(w->response.data, "error", "Failed to allocate MCP client");
191 w->response.code = HTTP_RESP_INTERNAL_SERVER_ERROR;
192 return w->response.code;
193 }
194 mcpc->user_auth = &w->user_auth;
195
196 int rc = mcp_sse_serialize_response(w, mcpc, root);
197
198 json_object_put(root);
199 mcp_free_client(mcpc);
200 return rc;
201 }