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:
authorYour Name <you@example.com>2026-05-17 05:27:06 +0530
committerYour Name <you@example.com>2026-05-17 05:27:06 +0530
commitfdf662f8f1a1a3b38fe4d251982fffab8e9bf664 (patch)
tree2413bdc936b757adf4849a522b7df2a5c8eb0aec /main/mcp_handler.c
parentedd125d0e3fe5fe7c0edf30c429723f3b0120c68 (diff)
Phase 7: MCP handler (25 tests), NIP-04 encrypt/decrypt (15 tests), CVM server skeleton
- mcp_handler.c/h: 4 tools (get_config, set_config, get_balance, wallet_send) - nip04.c/h: AES-256-CBC + ECDH with 0x02 compressed pubkey prefix - Fixed IV copy bug: mbedTLS AES-CBC modifies IV in-place - Base64 encode/decode for ciphertext transport - PKCS7 padding - cvm_server.c/h: Nostr DM listener with FreeRTOS task - config: cvm_enabled, cvm_relays fields - 156 total tests passing across 10 test binaries
Diffstat (limited to 'main/mcp_handler.c')
-rw-r--r--main/mcp_handler.c175
1 files changed, 175 insertions, 0 deletions
diff --git a/main/mcp_handler.c b/main/mcp_handler.c
new file mode 100644
index 0000000..f40c1bd
--- /dev/null
+++ b/main/mcp_handler.c
@@ -0,0 +1,175 @@
1#include "mcp_handler.h"
2#include "config.h"
3#include "nucula_wallet.h"
4#include "cJSON.h"
5#include <string.h>
6#include <stdio.h>
7
8static const char *TAG = "mcp_handler";
9
10mcp_tool_t mcp_parse_tool(const char *method)
11{
12 if (!method) return MCP_TOOL_UNKNOWN;
13 if (strcmp(method, "get_config") == 0) return MCP_TOOL_GET_CONFIG;
14 if (strcmp(method, "set_config") == 0) return MCP_TOOL_SET_CONFIG;
15 if (strcmp(method, "get_balance") == 0) return MCP_TOOL_GET_BALANCE;
16 if (strcmp(method, "wallet_send") == 0) return MCP_TOOL_WALLET_SEND;
17 return MCP_TOOL_UNKNOWN;
18}
19
20mcp_response_t mcp_handle_get_config(void)
21{
22 mcp_response_t resp = {0};
23 const tollgate_config_t *cfg = tollgate_config_get();
24 if (!cfg) {
25 resp.success = false;
26 snprintf(resp.error, sizeof(resp.error), "Config not loaded");
27 return resp;
28 }
29
30 cJSON *root = cJSON_CreateObject();
31 cJSON_AddStringToObject(root, "ssid", cfg->ap_ssid);
32 cJSON_AddStringToObject(root, "metric", cfg->metric);
33 cJSON_AddNumberToObject(root, "price_per_step", cfg->price_per_step);
34 cJSON_AddNumberToObject(root, "step_size_ms", cfg->step_size_ms);
35 cJSON_AddNumberToObject(root, "step_size_bytes", cfg->step_size_bytes);
36 cJSON_AddStringToObject(root, "mint_url", cfg->mint_url);
37 cJSON_AddBoolToObject(root, "client_enabled", cfg->client_enabled);
38 cJSON_AddBoolToObject(root, "payout_enabled", cfg->payout.enabled);
39 cJSON_AddStringToObject(root, "wifi_ssid",
40 cfg->network_count > 0 ? cfg->networks[0].ssid : "");
41
42 char *json = cJSON_PrintUnformatted(root);
43 snprintf(resp.result_json, sizeof(resp.result_json), "%s", json);
44 cJSON_free(json);
45 cJSON_Delete(root);
46 resp.success = true;
47 return resp;
48}
49
50mcp_response_t mcp_handle_set_config(const char *params_json)
51{
52 mcp_response_t resp = {0};
53 cJSON *root = cJSON_Parse(params_json);
54 if (!root) {
55 resp.success = false;
56 snprintf(resp.error, sizeof(resp.error), "Invalid JSON params");
57 return resp;
58 }
59
60 tollgate_config_t *cfg = (tollgate_config_t *)tollgate_config_get();
61 if (!cfg) {
62 cJSON_Delete(root);
63 resp.success = false;
64 snprintf(resp.error, sizeof(resp.error), "Config not loaded");
65 return resp;
66 }
67
68 cJSON *item;
69 item = cJSON_GetObjectItem(root, "price_per_step");
70 if (item && cJSON_IsNumber(item)) cfg->price_per_step = item->valueint;
71 item = cJSON_GetObjectItem(root, "step_size_ms");
72 if (item && cJSON_IsNumber(item)) cfg->step_size_ms = item->valueint;
73 item = cJSON_GetObjectItem(root, "step_size_bytes");
74 if (item && cJSON_IsNumber(item)) cfg->step_size_bytes = item->valueint;
75 item = cJSON_GetObjectItem(root, "client_enabled");
76 if (item && cJSON_IsBool(item)) cfg->client_enabled = cJSON_IsTrue(item);
77 item = cJSON_GetObjectItem(root, "payout_enabled");
78 if (item && cJSON_IsBool(item)) cfg->payout.enabled = cJSON_IsTrue(item);
79 item = cJSON_GetObjectItem(root, "metric");
80 if (item && cJSON_IsString(item)) {
81 strncpy(cfg->metric, item->valuestring, sizeof(cfg->metric) - 1);
82 }
83
84 cJSON_Delete(root);
85 resp.success = true;
86 snprintf(resp.result_json, sizeof(resp.result_json), "{\"status\":\"ok\"}");
87 return resp;
88}
89
90mcp_response_t mcp_handle_get_balance(void)
91{
92 mcp_response_t resp = {0};
93 uint64_t balance = nucula_wallet_balance();
94 int proof_count = nucula_wallet_proof_count();
95
96 cJSON *root = cJSON_CreateObject();
97 cJSON_AddNumberToObject(root, "balance_sats", (double)balance);
98 cJSON_AddNumberToObject(root, "proof_count", proof_count);
99
100 char *json = cJSON_PrintUnformatted(root);
101 snprintf(resp.result_json, sizeof(resp.result_json), "%s", json);
102 cJSON_free(json);
103 cJSON_Delete(root);
104 resp.success = true;
105 return resp;
106}
107
108mcp_response_t mcp_handle_wallet_send(const char *params_json)
109{
110 mcp_response_t resp = {0};
111 cJSON *root = cJSON_Parse(params_json);
112 if (!root) {
113 resp.success = false;
114 snprintf(resp.error, sizeof(resp.error), "Invalid JSON params");
115 return resp;
116 }
117
118 cJSON *amount_item = cJSON_GetObjectItem(root, "amount");
119 if (!amount_item || !cJSON_IsNumber(amount_item)) {
120 cJSON_Delete(root);
121 resp.success = false;
122 snprintf(resp.error, sizeof(resp.error), "Missing 'amount' field");
123 return resp;
124 }
125
126 uint64_t amount = (uint64_t)amount_item->valuedouble;
127 char token_out[2048] = {0};
128 int rc = nucula_wallet_send(amount, token_out, sizeof(token_out));
129
130 if (rc != 0) {
131 cJSON_Delete(root);
132 resp.success = false;
133 snprintf(resp.error, sizeof(resp.error), "Send failed: %d", rc);
134 return resp;
135 }
136
137 cJSON *result = cJSON_CreateObject();
138 cJSON_AddStringToObject(result, "token", token_out);
139 cJSON_AddNumberToObject(result, "amount", (double)amount);
140 char *json = cJSON_PrintUnformatted(result);
141 snprintf(resp.result_json, sizeof(resp.result_json), "%s", json);
142 cJSON_free(json);
143 cJSON_Delete(result);
144 cJSON_Delete(root);
145 resp.success = true;
146 return resp;
147}
148
149mcp_response_t mcp_dispatch(const mcp_request_t *req)
150{
151 if (!req) {
152 mcp_response_t resp = {0};
153 resp.success = false;
154 snprintf(resp.error, sizeof(resp.error), "NULL request");
155 return resp;
156 }
157
158 switch (req->tool) {
159 case MCP_TOOL_GET_CONFIG:
160 return mcp_handle_get_config();
161 case MCP_TOOL_SET_CONFIG:
162 return mcp_handle_set_config(req->params_json);
163 case MCP_TOOL_GET_BALANCE:
164 return mcp_handle_get_balance();
165 case MCP_TOOL_WALLET_SEND:
166 return mcp_handle_wallet_send(req->params_json);
167 default:
168 break;
169 }
170
171 mcp_response_t resp = {0};
172 resp.success = false;
173 snprintf(resp.error, sizeof(resp.error), "Unknown tool: %s", req->method);
174 return resp;
175}