upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/main/mcp_handler.c
diff options
context:
space:
mode:
Diffstat (limited to 'main/mcp_handler.c')
-rw-r--r--main/mcp_handler.c236
1 files changed, 236 insertions, 0 deletions
diff --git a/main/mcp_handler.c b/main/mcp_handler.c
index f40c1bd..93bfba9 100644
--- a/main/mcp_handler.c
+++ b/main/mcp_handler.c
@@ -1,7 +1,9 @@
1#include "mcp_handler.h" 1#include "mcp_handler.h"
2#include "config.h" 2#include "config.h"
3#include "nucula_wallet.h" 3#include "nucula_wallet.h"
4#include "session.h"
4#include "cJSON.h" 5#include "cJSON.h"
6#include "lwip/ip4_addr.h"
5#include <string.h> 7#include <string.h>
6#include <stdio.h> 8#include <stdio.h>
7 9
@@ -14,6 +16,12 @@ mcp_tool_t mcp_parse_tool(const char *method)
14 if (strcmp(method, "set_config") == 0) return MCP_TOOL_SET_CONFIG; 16 if (strcmp(method, "set_config") == 0) return MCP_TOOL_SET_CONFIG;
15 if (strcmp(method, "get_balance") == 0) return MCP_TOOL_GET_BALANCE; 17 if (strcmp(method, "get_balance") == 0) return MCP_TOOL_GET_BALANCE;
16 if (strcmp(method, "wallet_send") == 0) return MCP_TOOL_WALLET_SEND; 18 if (strcmp(method, "wallet_send") == 0) return MCP_TOOL_WALLET_SEND;
19 if (strcmp(method, "get_sessions") == 0) return MCP_TOOL_GET_SESSIONS;
20 if (strcmp(method, "get_usage") == 0) return MCP_TOOL_GET_USAGE;
21 if (strcmp(method, "set_payout") == 0) return MCP_TOOL_SET_PAYOUT;
22 if (strcmp(method, "set_metric") == 0) return MCP_TOOL_SET_METRIC;
23 if (strcmp(method, "set_price") == 0) return MCP_TOOL_SET_PRICE;
24 if (strcmp(method, "wallet_melt") == 0) return MCP_TOOL_WALLET_MELT;
17 return MCP_TOOL_UNKNOWN; 25 return MCP_TOOL_UNKNOWN;
18} 26}
19 27
@@ -146,6 +154,222 @@ mcp_response_t mcp_handle_wallet_send(const char *params_json)
146 return resp; 154 return resp;
147} 155}
148 156
157mcp_response_t mcp_handle_get_sessions(void)
158{
159 mcp_response_t resp = {0};
160 extern session_t *cvm_get_sessions_array(void);
161 extern int cvm_get_sessions_count(void);
162
163 cJSON *arr = cJSON_CreateArray();
164 int count = cvm_get_sessions_count();
165 session_t *sessions = cvm_get_sessions_array();
166
167 if (sessions && count > 0) {
168 for (int i = 0; i < count; i++) {
169 if (!sessions[i].active) continue;
170 cJSON *s = cJSON_CreateObject();
171 esp_ip4_addr_t ip = { .addr = sessions[i].client_ip };
172 char ip_str[16];
173 snprintf(ip_str, sizeof(ip_str), IPSTR, IP2STR(&ip));
174 cJSON_AddStringToObject(s, "client_ip", ip_str);
175 if (sessions[i].mac[0])
176 cJSON_AddStringToObject(s, "mac", sessions[i].mac);
177 cJSON_AddNumberToObject(s, "allotment_ms", (double)sessions[i].allotment_ms);
178 cJSON_AddNumberToObject(s, "allotment_bytes", (double)sessions[i].allotment_bytes);
179 cJSON_AddNumberToObject(s, "bytes_consumed", (double)sessions[i].bytes_consumed);
180 cJSON_AddBoolToObject(s, "active", sessions[i].active);
181 cJSON_AddItemToArray(arr, s);
182 }
183 }
184
185 char *json = cJSON_PrintUnformatted(arr);
186 snprintf(resp.result_json, sizeof(resp.result_json), "%s", json);
187 cJSON_free(json);
188 cJSON_Delete(arr);
189 resp.success = true;
190 return resp;
191}
192
193mcp_response_t mcp_handle_get_usage(void)
194{
195 mcp_response_t resp = {0};
196 const tollgate_config_t *cfg = tollgate_config_get();
197
198 cJSON *root = cJSON_CreateObject();
199 cJSON_AddStringToObject(root, "metric", cfg->metric);
200 cJSON_AddNumberToObject(root, "price_per_step", cfg->price_per_step);
201 cJSON_AddNumberToObject(root, "step_size_ms", cfg->step_size_ms);
202 cJSON_AddNumberToObject(root, "step_size_bytes", cfg->step_size_bytes);
203 cJSON_AddBoolToObject(root, "client_enabled", cfg->client_enabled);
204
205 char *json = cJSON_PrintUnformatted(root);
206 snprintf(resp.result_json, sizeof(resp.result_json), "%s", json);
207 cJSON_free(json);
208 cJSON_Delete(root);
209 resp.success = true;
210 return resp;
211}
212
213mcp_response_t mcp_handle_set_payout(const char *params_json)
214{
215 mcp_response_t resp = {0};
216 cJSON *root = cJSON_Parse(params_json);
217 if (!root) {
218 resp.success = false;
219 snprintf(resp.error, sizeof(resp.error), "Invalid JSON params");
220 return resp;
221 }
222
223 tollgate_config_t *cfg = (tollgate_config_t *)tollgate_config_get();
224 if (!cfg) {
225 cJSON_Delete(root);
226 resp.success = false;
227 snprintf(resp.error, sizeof(resp.error), "Config not loaded");
228 return resp;
229 }
230
231 cJSON *enabled = cJSON_GetObjectItem(root, "enabled");
232 if (enabled && cJSON_IsBool(enabled)) cfg->payout.enabled = cJSON_IsTrue(enabled);
233
234 cJSON *recipients = cJSON_GetObjectItem(root, "recipients");
235 if (recipients && cJSON_IsArray(recipients)) {
236 int rcount = cJSON_GetArraySize(recipients);
237 if (rcount > PAYOUT_MAX_RECIPIENTS) rcount = PAYOUT_MAX_RECIPIENTS;
238 for (int i = 0; i < rcount; i++) {
239 cJSON *r = cJSON_GetArrayItem(recipients, i);
240 cJSON *addr = cJSON_GetObjectItem(r, "lightning_address");
241 cJSON *factor = cJSON_GetObjectItem(r, "factor");
242 if (addr && cJSON_IsString(addr)) {
243 strncpy(cfg->payout.recipients[i].lightning_address, addr->valuestring,
244 sizeof(cfg->payout.recipients[i].lightning_address) - 1);
245 }
246 if (factor && cJSON_IsNumber(factor)) {
247 cfg->payout.recipients[i].factor = factor->valuedouble;
248 }
249 }
250 cfg->payout.recipient_count = rcount;
251 }
252
253 cJSON_Delete(root);
254 resp.success = true;
255 snprintf(resp.result_json, sizeof(resp.result_json), "{\"status\":\"ok\"}");
256 return resp;
257}
258
259mcp_response_t mcp_handle_set_metric(const char *params_json)
260{
261 mcp_response_t resp = {0};
262 cJSON *root = cJSON_Parse(params_json);
263 if (!root) {
264 resp.success = false;
265 snprintf(resp.error, sizeof(resp.error), "Invalid JSON params");
266 return resp;
267 }
268
269 tollgate_config_t *cfg = (tollgate_config_t *)tollgate_config_get();
270 if (!cfg) {
271 cJSON_Delete(root);
272 resp.success = false;
273 snprintf(resp.error, sizeof(resp.error), "Config not loaded");
274 return resp;
275 }
276
277 cJSON *metric = cJSON_GetObjectItem(root, "metric");
278 if (metric && cJSON_IsString(metric)) {
279 const char *m = metric->valuestring;
280 if (strcmp(m, "bytes") == 0 || strcmp(m, "milliseconds") == 0) {
281 strncpy(cfg->metric, m, sizeof(cfg->metric) - 1);
282 } else {
283 cJSON_Delete(root);
284 resp.success = false;
285 snprintf(resp.error, sizeof(resp.error), "Invalid metric: must be 'bytes' or 'milliseconds'");
286 return resp;
287 }
288 } else {
289 cJSON_Delete(root);
290 resp.success = false;
291 snprintf(resp.error, sizeof(resp.error), "Missing 'metric' field");
292 return resp;
293 }
294
295 cJSON_Delete(root);
296 resp.success = true;
297 snprintf(resp.result_json, sizeof(resp.result_json), "{\"status\":\"ok\",\"metric\":\"%s\"}", cfg->metric);
298 return resp;
299}
300
301mcp_response_t mcp_handle_set_price(const char *params_json)
302{
303 mcp_response_t resp = {0};
304 cJSON *root = cJSON_Parse(params_json);
305 if (!root) {
306 resp.success = false;
307 snprintf(resp.error, sizeof(resp.error), "Invalid JSON params");
308 return resp;
309 }
310
311 tollgate_config_t *cfg = (tollgate_config_t *)tollgate_config_get();
312 if (!cfg) {
313 cJSON_Delete(root);
314 resp.success = false;
315 snprintf(resp.error, sizeof(resp.error), "Config not loaded");
316 return resp;
317 }
318
319 cJSON *price = cJSON_GetObjectItem(root, "price_per_step");
320 if (price && cJSON_IsNumber(price) && price->valueint > 0) {
321 cfg->price_per_step = price->valueint;
322 } else {
323 cJSON_Delete(root);
324 resp.success = false;
325 snprintf(resp.error, sizeof(resp.error), "Missing or invalid 'price_per_step' field");
326 return resp;
327 }
328
329 cJSON_Delete(root);
330 resp.success = true;
331 snprintf(resp.result_json, sizeof(resp.result_json),
332 "{\"status\":\"ok\",\"price_per_step\":%d}", cfg->price_per_step);
333 return resp;
334}
335
336mcp_response_t mcp_handle_wallet_melt(const char *params_json)
337{
338 mcp_response_t resp = {0};
339 cJSON *root = cJSON_Parse(params_json);
340 if (!root) {
341 resp.success = false;
342 snprintf(resp.error, sizeof(resp.error), "Invalid JSON params");
343 return resp;
344 }
345
346 cJSON *bolt11 = cJSON_GetObjectItem(root, "bolt11");
347 if (!bolt11 || !cJSON_IsString(bolt11)) {
348 cJSON_Delete(root);
349 resp.success = false;
350 snprintf(resp.error, sizeof(resp.error), "Missing 'bolt11' field");
351 return resp;
352 }
353
354 cJSON *max_fee = cJSON_GetObjectItem(root, "max_fee_sats");
355 uint64_t fee = 10;
356 if (max_fee && cJSON_IsNumber(max_fee)) fee = (uint64_t)max_fee->valuedouble;
357
358 esp_err_t rc = nucula_wallet_melt(bolt11->valuestring, fee);
359
360 if (rc != ESP_OK) {
361 cJSON_Delete(root);
362 resp.success = false;
363 snprintf(resp.error, sizeof(resp.error), "Melt failed: %s", esp_err_to_name(rc));
364 return resp;
365 }
366
367 cJSON_Delete(root);
368 resp.success = true;
369 snprintf(resp.result_json, sizeof(resp.result_json), "{\"status\":\"ok\"}");
370 return resp;
371}
372
149mcp_response_t mcp_dispatch(const mcp_request_t *req) 373mcp_response_t mcp_dispatch(const mcp_request_t *req)
150{ 374{
151 if (!req) { 375 if (!req) {
@@ -164,6 +388,18 @@ mcp_response_t mcp_dispatch(const mcp_request_t *req)
164 return mcp_handle_get_balance(); 388 return mcp_handle_get_balance();
165 case MCP_TOOL_WALLET_SEND: 389 case MCP_TOOL_WALLET_SEND:
166 return mcp_handle_wallet_send(req->params_json); 390 return mcp_handle_wallet_send(req->params_json);
391 case MCP_TOOL_GET_SESSIONS:
392 return mcp_handle_get_sessions();
393 case MCP_TOOL_GET_USAGE:
394 return mcp_handle_get_usage();
395 case MCP_TOOL_SET_PAYOUT:
396 return mcp_handle_set_payout(req->params_json);
397 case MCP_TOOL_SET_METRIC:
398 return mcp_handle_set_metric(req->params_json);
399 case MCP_TOOL_SET_PRICE:
400 return mcp_handle_set_price(req->params_json);
401 case MCP_TOOL_WALLET_MELT:
402 return mcp_handle_wallet_melt(req->params_json);
167 default: 403 default:
168 break; 404 break;
169 } 405 }