master
c 192 lines 6.31 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "mcp-websocket.h"
4 #include "web/websocket/websocket-internal.h"
5 #include "web/mcp/mcp-jsonrpc.h"
6
7 #include <string.h>
8
9 // Store the MCP context in the WebSocket client's data field
10 void mcp_websocket_set_context(struct websocket_server_client *wsc, MCP_CLIENT *ctx) {
11 if (!wsc) return;
12 wsc->user_data = ctx;
13 }
14
15 // Get the MCP context from a WebSocket client
16 MCP_CLIENT *mcp_websocket_get_context(struct websocket_server_client *wsc) {
17 if (!wsc) return NULL;
18 return (MCP_CLIENT *)wsc->user_data;
19 }
20
21 // Create a response context for a WebSocket client
22 static MCP_CLIENT *mcp_websocket_create_context(struct websocket_server_client *wsc) {
23 if (!wsc) return NULL;
24
25 MCP_CLIENT *ctx = mcp_create_client(MCP_TRANSPORT_WEBSOCKET, wsc);
26 if (ctx) {
27 // Set pointer to the websocket client's user_auth
28 ctx->user_auth = &wsc->user_auth;
29 }
30 mcp_websocket_set_context(wsc, ctx);
31
32 return ctx;
33 }
34
35 // WebSocket connection handler for MCP
36 void mcp_websocket_on_connect(struct websocket_server_client *wsc) {
37 if (!wsc) return;
38
39 // Create the MCP context
40 MCP_CLIENT *ctx = mcp_websocket_create_context(wsc);
41 if (!ctx) {
42 websocket_protocol_send_close(wsc, WS_CLOSE_INTERNAL_ERROR, "Failed to create MCP context");
43 return;
44 }
45
46 websocket_debug(wsc, "MCP client connected");
47 }
48
49 static void mcp_websocket_send_payload(struct websocket_server_client *wsc, BUFFER *payload) {
50 if (!wsc || !payload)
51 return;
52
53 const char *text = buffer_tostring(payload);
54 if (!text)
55 return;
56
57 netdata_log_debug(D_MCP, "SND: %s", text);
58 websocket_protocol_send_text(wsc, text);
59 }
60
61
62 // WebSocket message handler for MCP - receives message and routes to MCP
63 void mcp_websocket_on_message(struct websocket_server_client *wsc, const char *message, size_t length, WEBSOCKET_OPCODE opcode) {
64 if (!wsc || !message || length == 0)
65 return;
66
67 // Log the raw incoming message
68 netdata_log_debug(D_MCP, "RCV: %s", message);
69
70 // Only handle text messages
71 if (opcode != WS_OPCODE_TEXT) {
72 websocket_error(wsc, "Ignoring binary message - mcp supports only TEXT messages");
73 return;
74 }
75
76 // Silently ignore standalone "PING" messages (legacy MCP client behavior)
77 if (length == 4 && strncmp(message, "PING", 4) == 0) {
78 websocket_debug(wsc, "Ignoring legacy PING message");
79 return;
80 }
81
82 // Get the MCP context
83 MCP_CLIENT *mcpc = mcp_websocket_get_context(wsc);
84 if (!mcpc) {
85 websocket_error(wsc, "MCP context not found");
86 return;
87 }
88
89 // Parse the JSON-RPC request
90 struct json_object *request = NULL;
91 enum json_tokener_error jerr = json_tokener_success;
92 request = json_tokener_parse_verbose(message, &jerr);
93
94 if (!request || jerr != json_tokener_success) {
95 websocket_error(wsc, "Failed to parse JSON-RPC request: %s", json_tokener_error_desc(jerr));
96
97 BUFFER *error_payload = mcp_jsonrpc_build_error_payload(NULL, -32700, "Parse error", NULL, 0);
98 mcp_websocket_send_payload(wsc, error_payload);
99 buffer_free(error_payload);
100 return;
101 }
102
103 if (json_object_is_type(request, json_type_array)) {
104 int len = (int)json_object_array_length(request);
105 BUFFER **responses = NULL;
106 size_t responses_used = 0;
107 size_t responses_size = 0;
108
109 for (int i = 0; i < len; i++) {
110 struct json_object *req_item = json_object_array_get_idx(request, i);
111 BUFFER *resp_item = mcp_jsonrpc_process_single_request(mcpc, req_item, NULL);
112 if (resp_item) {
113 if (responses_used == responses_size) {
114 size_t new_size = responses_size ? responses_size * 2 : 4;
115 BUFFER **tmp = reallocz(responses, new_size * sizeof(*tmp));
116 if (!tmp) {
117 buffer_free(resp_item);
118 continue;
119 }
120 responses = tmp;
121 responses_size = new_size;
122 }
123 responses[responses_used++] = resp_item;
124 }
125 }
126
127 if (responses_used > 0) {
128 size_t total_len = 2; // brackets
129 for (size_t i = 0; i < responses_used; i++)
130 total_len += buffer_strlen(responses[i]) + (i ? 1 : 0);
131
132 BUFFER *batch = buffer_create(total_len + 32, NULL);
133 buffer_fast_strcat(batch, "[", 1);
134 for (size_t i = 0; i < responses_used; i++) {
135 if (i)
136 buffer_fast_strcat(batch, ",", 1);
137 const char *resp_text = buffer_tostring(responses[i]);
138 size_t resp_len = buffer_strlen(responses[i]);
139 buffer_fast_strcat(batch, resp_text, resp_len);
140 }
141 buffer_fast_strcat(batch, "]", 1);
142 mcp_websocket_send_payload(wsc, batch);
143 buffer_free(batch);
144 }
145
146 for (size_t i = 0; i < responses_used; i++)
147 buffer_free(responses[i]);
148 freez(responses);
149 } else {
150 BUFFER *response = mcp_jsonrpc_process_single_request(mcpc, request, NULL);
151 if (response) {
152 mcp_websocket_send_payload(wsc, response);
153 buffer_free(response);
154 }
155 }
156
157 json_object_put(request);
158 }
159
160 // WebSocket close handler for MCP
161 void mcp_websocket_on_close(struct websocket_server_client *wsc, WEBSOCKET_CLOSE_CODE code, const char *reason) {
162 if (!wsc) return;
163
164 websocket_debug(wsc, "MCP client closing (code: %d, reason: %s)", code, reason ? reason : "none");
165
166 // Clean up the MCP context
167 MCP_CLIENT *ctx = mcp_websocket_get_context(wsc);
168 if (ctx) {
169 mcp_free_client(ctx);
170 mcp_websocket_set_context(wsc, NULL);
171 }
172 }
173
174 // WebSocket disconnect handler for MCP
175 void mcp_websocket_on_disconnect(struct websocket_server_client *wsc) {
176 if (!wsc) return;
177
178 websocket_debug(wsc, "MCP client disconnected");
179
180 // Clean up the MCP context
181 MCP_CLIENT *ctx = mcp_websocket_get_context(wsc);
182 if (ctx) {
183 mcp_free_client(ctx);
184 mcp_websocket_set_context(wsc, NULL);
185 }
186 }
187
188 // Register WebSocket callbacks for MCP
189 void mcp_websocket_adapter_initialize(void) {
190 mcp_initialize_subsystem();
191 netdata_log_info("MCP WebSocket adapter initialized");
192 }