Mercurial
comparison npc/main.c @ 181:a2720eac50ce
[NPC] Adding JSON RPC protocol for MPC endpoints
| author | June Park <parkjune1995@gmail.com> |
|---|---|
| date | Fri, 23 Jan 2026 21:07:08 -0800 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| 180:3a4ebe4552bf | 181:a2720eac50ce |
|---|---|
| 1 #include "seobeo/seobeo.h" | |
| 2 #include <string.h> | |
| 3 #include <time.h> | |
| 4 | |
| 5 static _Atomic uint32_t counter = 0; | |
| 6 | |
| 7 static void build_response(char *out, size_t size, const char *id, const char *result) { | |
| 8 snprintf(out, size, "{\"jsonrpc\":\"2.0\",\"id\":%s,\"result\":%s}", id, result); | |
| 9 } | |
| 10 | |
| 11 static void build_error(char *out, size_t size, const char *id, int code, const char *msg) { | |
| 12 snprintf(out, size, | |
| 13 "{\"jsonrpc\":\"2.0\",\"id\":%s,\"error\":{\"code\":%d,\"message\":\"%s\"}}", | |
| 14 id, code, msg); | |
| 15 } | |
| 16 | |
| 17 static void handle_initialize(char *out, size_t size, const char *id) { | |
| 18 const char *result = "{" | |
| 19 "\"protocolVersion\":\"2024-11-05\"," | |
| 20 "\"capabilities\":{\"tools\":{}}," | |
| 21 "\"serverInfo\":{\"name\":\"simple-mcp\",\"version\":\"1.0.0\"}" | |
| 22 "}"; | |
| 23 build_response(out, size, id, result); | |
| 24 } | |
| 25 | |
| 26 static void handle_tools_list(char *out, size_t size, const char *id) { | |
| 27 const char *result = "{" | |
| 28 "\"tools\":[" | |
| 29 "{" | |
| 30 "\"name\":\"get_time\"," | |
| 31 "\"description\":\"Get current Unix timestamp\"," | |
| 32 "\"inputSchema\":{\"type\":\"object\",\"properties\":{}}" | |
| 33 "}," | |
| 34 "{" | |
| 35 "\"name\":\"get_counter\"," | |
| 36 "\"description\":\"Get and increment a counter\"," | |
| 37 "\"inputSchema\":{\"type\":\"object\",\"properties\":{}}" | |
| 38 "}" | |
| 39 "]" | |
| 40 "}"; | |
| 41 build_response(out, size, id, result); | |
| 42 } | |
| 43 | |
| 44 static void handle_tools_call(char *out, size_t size, const char *id, Dowa_JSON_Entry *json) { | |
| 45 Dowa_JSON_Value *params_val = Dowa_JSON_Get(json, "params"); | |
| 46 if (!params_val || params_val->type != DOWA_JSON_OBJECT) | |
| 47 { | |
| 48 build_error(out, size, id, -32602, "Missing params"); | |
| 49 return; | |
| 50 } | |
| 51 | |
| 52 Dowa_JSON_Entry *params = (Dowa_JSON_Entry *)params_val->object_val; | |
| 53 char *tool_name = Dowa_JSON_Get_String(params, "name"); | |
| 54 | |
| 55 if (!tool_name) { | |
| 56 build_error(out, size, id, -32602, "Missing tool name"); | |
| 57 return; | |
| 58 } | |
| 59 | |
| 60 char result[256]; | |
| 61 if (strcmp(tool_name, "get_time") == 0) { | |
| 62 time_t now = time(NULL); | |
| 63 snprintf(result, sizeof(result), | |
| 64 "{\"content\":[{\"type\":\"text\",\"text\":\"Current timestamp: %ld\"}]}", | |
| 65 (long)now); | |
| 66 } else if (strcmp(tool_name, "get_counter") == 0) { | |
| 67 uint32_t val = ++counter; | |
| 68 snprintf(result, sizeof(result), | |
| 69 "{\"content\":[{\"type\":\"text\",\"text\":\"Counter value: %u\"}]}", | |
| 70 val); | |
| 71 } else { | |
| 72 build_error(out, size, id, -32601, "Unknown tool"); | |
| 73 return; | |
| 74 } | |
| 75 | |
| 76 build_response(out, size, id, result); | |
| 77 } | |
| 78 | |
| 79 Seobeo_Request_Entry* HandleMCP(Seobeo_Request_Entry *req, Dowa_Arena *arena) | |
| 80 { | |
| 81 Seobeo_Request_Entry *resp = NULL; | |
| 82 | |
| 83 void *body_kv = Dowa_HashMap_Get_Ptr(req, "Body"); | |
| 84 if (!body_kv) | |
| 85 { | |
| 86 char *err = "{\"jsonrpc\":\"2.0\",\"id\":null,\"error\":{\"code\":-32700,\"message\":\"No body\"}}"; | |
| 87 Dowa_HashMap_Push_Arena(resp, "status", "400", arena); | |
| 88 Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena); | |
| 89 Dowa_HashMap_Push_Arena(resp, "body", err, arena); | |
| 90 return resp; | |
| 91 } | |
| 92 | |
| 93 const char *body = ((Seobeo_Request_Entry*)body_kv)->value; | |
| 94 int32 body_len = strlen(body); | |
| 95 | |
| 96 Dowa_JSON_Value parsed = Dowa_JSON_Parse(body, body_len, arena); | |
| 97 if (parsed.type != DOWA_JSON_OBJECT || !parsed.object_val) | |
| 98 { | |
| 99 char *err = "{\"jsonrpc\":\"2.0\",\"id\":null,\"error\":{\"code\":-32700,\"message\":\"Parse error\"}}"; | |
| 100 Dowa_HashMap_Push_Arena(resp, "status", "400", arena); | |
| 101 Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena); | |
| 102 Dowa_HashMap_Push_Arena(resp, "body", err, arena); | |
| 103 return resp; | |
| 104 } | |
| 105 | |
| 106 Dowa_JSON_Entry *json = (Dowa_JSON_Entry *)parsed.object_val; | |
| 107 char *method = Dowa_JSON_Get_String(json, "method"); | |
| 108 Dowa_JSON_Value *id_val = Dowa_JSON_Get(json, "id"); | |
| 109 char id_buf[32] = "null"; | |
| 110 if (id_val) | |
| 111 { | |
| 112 if (id_val->type == DOWA_JSON_NUMBER) | |
| 113 snprintf(id_buf, sizeof(id_buf), "%.0f", id_val->num_val); | |
| 114 else if (id_val->type == DOWA_JSON_STRING) | |
| 115 snprintf(id_buf, sizeof(id_buf), "\"%s\"", id_val->str_val); | |
| 116 } | |
| 117 | |
| 118 char *response = Dowa_Arena_Allocate(arena, 2048); | |
| 119 | |
| 120 if (!method) | |
| 121 build_error(response, 2048, id_buf, -32600, "Missing method"); | |
| 122 else if (strcmp(method, "initialize") == 0) | |
| 123 handle_initialize(response, 2048, id_buf); | |
| 124 else if (strcmp(method, "tools/list") == 0) | |
| 125 handle_tools_list(response, 2048, id_buf); | |
| 126 else if (strcmp(method, "tools/call") == 0) | |
| 127 handle_tools_call(response, 2048, id_buf, json); | |
| 128 else if (strcmp(method, "notifications/initialized") == 0) | |
| 129 snprintf(response, 2048, "{\"jsonrpc\":\"2.0\",\"id\":%s,\"result\":{}}", id_buf); | |
| 130 else | |
| 131 build_error(response, 2048, id_buf, -32601, "Method not found"); | |
| 132 | |
| 133 Dowa_HashMap_Push_Arena(resp, "status", "200", arena); | |
| 134 Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena); | |
| 135 Dowa_HashMap_Push_Arena(resp, "body", response, arena); | |
| 136 return resp; | |
| 137 } | |
| 138 | |
| 139 int main(void) { | |
| 140 Seobeo_Router_Init(); | |
| 141 Seobeo_Router_Register("POST", "/mcp", HandleMCP); | |
| 142 | |
| 143 Seobeo_Log(SEOBEO_INFO, "MCP server running on http://localhost:8080/mcp\n"); | |
| 144 Seobeo_Web_Server_Start(NULL, "8080", SEOBEO_MODE_FORK, 0); | |
| 145 return 0; | |
| 146 } |