diff options
| author | Your Name <you@example.com> | 2026-05-17 05:27:06 +0530 |
|---|---|---|
| committer | Your Name <you@example.com> | 2026-05-17 05:27:06 +0530 |
| commit | fdf662f8f1a1a3b38fe4d251982fffab8e9bf664 (patch) | |
| tree | 2413bdc936b757adf4849a522b7df2a5c8eb0aec /main/mcp_handler.c | |
| parent | edd125d0e3fe5fe7c0edf30c429723f3b0120c68 (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.c | 175 |
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 | |||
| 8 | static const char *TAG = "mcp_handler"; | ||
| 9 | |||
| 10 | mcp_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 | |||
| 20 | mcp_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 | |||
| 50 | mcp_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 | |||
| 90 | mcp_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 | |||
| 108 | mcp_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 | |||
| 149 | mcp_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 | } | ||