* Copyright (C) 2026 Xiaomi Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "tools/mcp_server.h"
#include "tools/tool_registry.h"
#include "agent_config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <syslog.h>
#include <sys/socket.h>
#include "cJSON.h"
#define JSONRPC_PARSE_ERROR -32700
#define JSONRPC_INVALID_REQUEST -32600
#define JSONRPC_METHOD_NOT_FOUND -32601
#define JSONRPC_INVALID_PARAMS -32602
#define JSONRPC_INTERNAL_ERROR -32603
static char *create_error_response(const char *id, int code, const char *message)
{
cJSON *root = cJSON_CreateObject();
cJSON_AddStringToObject(root, "jsonrpc", "2.0");
cJSON *error = cJSON_CreateObject();
cJSON_AddNumberToObject(error, "code", code);
cJSON_AddStringToObject(error, "message", message);
cJSON_AddItemToObject(root, "error", error);
if (id) cJSON_AddStringToObject(root, "id", id);
else cJSON_AddNullToObject(root, "id");
char *json = cJSON_PrintUnformatted(root);
cJSON_Delete(root);
return json;
}
static char *create_success_response(const char *id, cJSON *result)
{
cJSON *root = cJSON_CreateObject();
cJSON_AddStringToObject(root, "jsonrpc", "2.0");
cJSON_AddItemToObject(root, "result", result);
if (id) cJSON_AddStringToObject(root, "id", id);
else cJSON_AddNullToObject(root, "id");
char *json = cJSON_PrintUnformatted(root);
cJSON_Delete(root);
return json;
}
static char *handle_tools_list(mcp_server_t *server, const char *id)
{
pthread_rwlock_rdlock(&server->tool_lock);
cJSON *tools_array = cJSON_CreateArray();
for (int i = 0; i < server->tool_count; i++) {
mcp_tool_t *tool = &server->tools[i];
cJSON *tool_obj = cJSON_CreateObject();
cJSON_AddStringToObject(tool_obj, "name", tool->name);
cJSON_AddStringToObject(tool_obj, "description", tool->description);
if (tool->param_count > 0) {
cJSON *params = cJSON_CreateObject();
cJSON *properties = cJSON_CreateObject();
cJSON *required = cJSON_CreateArray();
for (int j = 0; j < tool->param_count; j++) {
cJSON *p = cJSON_CreateObject();
cJSON_AddStringToObject(p, "type", "string");
cJSON_AddStringToObject(p, "description", tool->params[j].description);
cJSON_AddItemToObject(properties, tool->params[j].name, p);
if (tool->params[j].required)
cJSON_AddItemToArray(required, cJSON_CreateString(tool->params[j].name));
}
cJSON_AddStringToObject(params, "type", "object");
cJSON_AddItemToObject(params, "properties", properties);
if (cJSON_GetArraySize(required) > 0)
cJSON_AddItemToObject(params, "required", required);
else
cJSON_Delete(required);
cJSON_AddItemToObject(tool_obj, "inputSchema", params);
}
cJSON_AddItemToArray(tools_array, tool_obj);
}
pthread_rwlock_unlock(&server->tool_lock);
cJSON *result = cJSON_CreateObject();
cJSON_AddItemToObject(result, "tools", tools_array);
return create_success_response(id, result);
}
static char *handle_tools_call(mcp_server_t *server, const char *id, cJSON *params)
{
cJSON *name_obj = cJSON_GetObjectItem(params, "name");
cJSON *args_obj = cJSON_GetObjectItem(params, "arguments");
if (!name_obj || !cJSON_IsString(name_obj))
return create_error_response(id, JSONRPC_INVALID_PARAMS, "Missing tool name");
const char *tool_name = name_obj->valuestring;
char *args_json = args_obj ? cJSON_PrintUnformatted(args_obj) : strdup("{}");
pthread_rwlock_rdlock(&server->tool_lock);
mcp_tool_t *tool = NULL;
for (int i = 0; i < server->tool_count; i++) {
if (strcmp(server->tools[i].name, tool_name) == 0) {
tool = &server->tools[i];
break;
}
}
if (!tool) {
pthread_rwlock_unlock(&server->tool_lock);
free(args_json);
return create_error_response(id, JSONRPC_METHOD_NOT_FOUND, "Tool not found");
}
tool->call_count++;
char *result_str = tool->callback(args_json, tool->user_data);
pthread_rwlock_unlock(&server->tool_lock);
free(args_json);
if (!result_str)
return create_error_response(id, JSONRPC_INTERNAL_ERROR, "Tool execution failed");
cJSON *result = cJSON_CreateObject();
cJSON *content_array = cJSON_CreateArray();
cJSON *content_obj = cJSON_CreateObject();
cJSON_AddStringToObject(content_obj, "type", "text");
cJSON_AddStringToObject(content_obj, "text", result_str);
cJSON_AddItemToArray(content_array, content_obj);
cJSON_AddItemToObject(result, "content", content_array);
free(result_str);
return create_success_response(id, result);
}
static char *handle_resources_list(mcp_server_t *server, const char *id)
{
pthread_rwlock_rdlock(&server->resource_lock);
cJSON *arr = cJSON_CreateArray();
for (int i = 0; i < server->resource_count; i++) {
mcp_resource_t *r = &server->resources[i];
cJSON *obj = cJSON_CreateObject();
cJSON_AddStringToObject(obj, "uri", r->uri);
cJSON_AddStringToObject(obj, "name", r->name);
cJSON_AddStringToObject(obj, "description", r->description);
cJSON_AddStringToObject(obj, "mimeType", r->mime_type);
cJSON_AddItemToArray(arr, obj);
}
pthread_rwlock_unlock(&server->resource_lock);
cJSON *result = cJSON_CreateObject();
cJSON_AddItemToObject(result, "resources", arr);
return create_success_response(id, result);
}
static char *handle_resources_read(mcp_server_t *server, const char *id, cJSON *params)
{
cJSON *uri_obj = cJSON_GetObjectItem(params, "uri");
if (!uri_obj || !cJSON_IsString(uri_obj))
return create_error_response(id, JSONRPC_INVALID_PARAMS, "Missing resource URI");
const char *uri = uri_obj->valuestring;
pthread_rwlock_rdlock(&server->resource_lock);
mcp_resource_t *res = NULL;
for (int i = 0; i < server->resource_count; i++) {
if (strcmp(server->resources[i].uri, uri) == 0) { res = &server->resources[i]; break; }
}
if (!res) {
pthread_rwlock_unlock(&server->resource_lock);
return create_error_response(id, JSONRPC_METHOD_NOT_FOUND, "Resource not found");
}
char *content = res->read_callback(uri, res->user_data);
pthread_rwlock_unlock(&server->resource_lock);
if (!content)
return create_error_response(id, JSONRPC_INTERNAL_ERROR, "Failed to read resource");
cJSON *result = cJSON_CreateObject();
cJSON *contents = cJSON_CreateArray();
cJSON *c = cJSON_CreateObject();
cJSON_AddStringToObject(c, "uri", uri);
cJSON_AddStringToObject(c, "mimeType", res->mime_type);
cJSON_AddStringToObject(c, "text", content);
cJSON_AddItemToArray(contents, c);
cJSON_AddItemToObject(result, "contents", contents);
free(content);
return create_success_response(id, result);
}
static char *handle_prompts_list(mcp_server_t *server, const char *id)
{
pthread_rwlock_rdlock(&server->prompt_lock);
cJSON *arr = cJSON_CreateArray();
for (int i = 0; i < server->prompt_count; i++) {
mcp_prompt_t *p = &server->prompts[i];
cJSON *obj = cJSON_CreateObject();
cJSON_AddStringToObject(obj, "name", p->name);
cJSON_AddStringToObject(obj, "description", p->description);
if (p->arg_count > 0) {
cJSON *args = cJSON_CreateArray();
for (int j = 0; j < p->arg_count; j++) {
cJSON *a = cJSON_CreateObject();
cJSON_AddStringToObject(a, "name", p->args[j].name);
cJSON_AddStringToObject(a, "description", p->args[j].description);
cJSON_AddBoolToObject(a, "required", p->args[j].required);
cJSON_AddItemToArray(args, a);
}
cJSON_AddItemToObject(obj, "arguments", args);
}
cJSON_AddItemToArray(arr, obj);
}
pthread_rwlock_unlock(&server->prompt_lock);
cJSON *result = cJSON_CreateObject();
cJSON_AddItemToObject(result, "prompts", arr);
return create_success_response(id, result);
}
static char *handle_prompts_get(mcp_server_t *server, const char *id, cJSON *params)
{
cJSON *name_obj = cJSON_GetObjectItem(params, "name");
if (!name_obj || !cJSON_IsString(name_obj))
return create_error_response(id, JSONRPC_INVALID_PARAMS, "Missing prompt name");
const char *prompt_name = name_obj->valuestring;
cJSON *args_obj = cJSON_GetObjectItem(params, "arguments");
char *args_json = args_obj ? cJSON_PrintUnformatted(args_obj) : NULL;
pthread_rwlock_rdlock(&server->prompt_lock);
mcp_prompt_t *prompt = NULL;
for (int i = 0; i < server->prompt_count; i++) {
if (strcmp(server->prompts[i].name, prompt_name) == 0) { prompt = &server->prompts[i]; break; }
}
if (!prompt) {
pthread_rwlock_unlock(&server->prompt_lock);
free(args_json);
return create_error_response(id, JSONRPC_METHOD_NOT_FOUND, "Prompt not found");
}
int msg_count = 0;
mcp_prompt_message_t *msgs = prompt->callback(prompt_name, args_json, &msg_count, prompt->user_data);
pthread_rwlock_unlock(&server->prompt_lock);
free(args_json);
if (!msgs || msg_count == 0)
return create_error_response(id, JSONRPC_INTERNAL_ERROR, "Failed to generate prompt");
cJSON *result = cJSON_CreateObject();
cJSON *messages = cJSON_CreateArray();
for (int i = 0; i < msg_count; i++) {
cJSON *m = cJSON_CreateObject();
cJSON_AddStringToObject(m, "role", msgs[i].role);
cJSON *co = cJSON_CreateObject();
cJSON_AddStringToObject(co, "type", "text");
cJSON_AddStringToObject(co, "text", msgs[i].content);
cJSON_AddItemToObject(m, "content", co);
cJSON_AddItemToArray(messages, m);
free(msgs[i].content);
}
free(msgs);
cJSON_AddItemToObject(result, "messages", messages);
return create_success_response(id, result);
}
int mcp_server_init(mcp_server_t *server, const char *name, const char *version)
{
if (!server || !name || !version) return -1;
memset(server, 0, sizeof(*server));
strncpy(server->name, name, sizeof(server->name) - 1);
strncpy(server->version, version, sizeof(server->version) - 1);
pthread_rwlock_init(&server->tool_lock, NULL);
pthread_rwlock_init(&server->resource_lock, NULL);
pthread_rwlock_init(&server->prompt_lock, NULL);
server->initialized = true;
return 0;
}
void mcp_server_destroy(mcp_server_t *server)
{
if (!server || !server->initialized) return;
if (server->stdio_running) mcp_server_stop_stdio(server);
pthread_rwlock_destroy(&server->tool_lock);
pthread_rwlock_destroy(&server->resource_lock);
pthread_rwlock_destroy(&server->prompt_lock);
server->initialized = false;
}
int mcp_server_register_tool(mcp_server_t *server,
const char *name, const char *description,
const mcp_param_t *params, int param_count,
mcp_tool_fn callback, void *user_data,
bool is_streaming)
{
if (!server || !name || !callback || param_count > MCP_MAX_PARAMS) return -1;
pthread_rwlock_wrlock(&server->tool_lock);
if (server->tool_count >= MCP_MAX_TOOLS) {
pthread_rwlock_unlock(&server->tool_lock);
return -1;
}
mcp_tool_t *t = &server->tools[server->tool_count];
strncpy(t->name, name, sizeof(t->name) - 1);
strncpy(t->description, description ? description : "", sizeof(t->description) - 1);
t->param_count = param_count;
if (params && param_count > 0) memcpy(t->params, params, param_count * sizeof(mcp_param_t));
t->callback = callback;
t->user_data = user_data;
t->is_streaming = is_streaming;
t->call_count = 0;
server->tool_count++;
pthread_rwlock_unlock(&server->tool_lock);
return 0;
}
int mcp_server_register_resource(mcp_server_t *server,
const char *uri, const char *name,
const char *description, const char *mime_type,
mcp_resource_fn callback, void *user_data)
{
if (!server || !uri || !name || !callback) return -1;
pthread_rwlock_wrlock(&server->resource_lock);
if (server->resource_count >= MCP_MAX_RESOURCES) {
pthread_rwlock_unlock(&server->resource_lock);
return -1;
}
mcp_resource_t *r = &server->resources[server->resource_count];
strncpy(r->uri, uri, sizeof(r->uri) - 1);
strncpy(r->name, name, sizeof(r->name) - 1);
strncpy(r->description, description ? description : "", sizeof(r->description) - 1);
strncpy(r->mime_type, mime_type ? mime_type : "text/plain", sizeof(r->mime_type) - 1);
r->read_callback = callback;
r->user_data = user_data;
server->resource_count++;
pthread_rwlock_unlock(&server->resource_lock);
return 0;
}
int mcp_server_register_prompt(mcp_server_t *server,
const char *name, const char *description,
const mcp_param_t *args, int arg_count,
mcp_prompt_fn callback, void *user_data)
{
if (!server || !name || !callback || arg_count > MCP_MAX_PARAMS) return -1;
pthread_rwlock_wrlock(&server->prompt_lock);
if (server->prompt_count >= MCP_MAX_PROMPTS) {
pthread_rwlock_unlock(&server->prompt_lock);
return -1;
}
mcp_prompt_t *p = &server->prompts[server->prompt_count];
strncpy(p->name, name, sizeof(p->name) - 1);
strncpy(p->description, description ? description : "", sizeof(p->description) - 1);
p->arg_count = arg_count;
if (args && arg_count > 0) memcpy(p->args, args, arg_count * sizeof(mcp_param_t));
p->callback = callback;
p->user_data = user_data;
server->prompt_count++;
pthread_rwlock_unlock(&server->prompt_lock);
return 0;
}
char *mcp_server_handle_request(mcp_server_t *server, const char *request_json)
{
if (!server || !request_json)
return create_error_response(NULL, JSONRPC_INVALID_REQUEST, "Invalid request");
cJSON *root = cJSON_Parse(request_json);
if (!root) return create_error_response(NULL, JSONRPC_PARSE_ERROR, "Parse error");
cJSON *method = cJSON_GetObjectItem(root, "method");
cJSON *params = cJSON_GetObjectItem(root, "params");
cJSON *id = cJSON_GetObjectItem(root, "id");
const char *id_str = NULL;
static char id_buf[32];
if (id && cJSON_IsString(id)) id_str = id->valuestring;
else if (id && cJSON_IsNumber(id)) { snprintf(id_buf, sizeof(id_buf), "%lld", (long long)id->valueint); id_str = id_buf; }
if (!method || !cJSON_IsString(method)) {
cJSON_Delete(root);
return create_error_response(id_str, JSONRPC_INVALID_REQUEST, "Invalid request");
}
const char *m = method->valuestring;
char *response = NULL;
if (strcmp(m, "tools/list") == 0) response = handle_tools_list(server, id_str);
else if (strcmp(m, "tools/call") == 0) response = handle_tools_call(server, id_str, params);
else if (strcmp(m, "resources/list") == 0) response = handle_resources_list(server, id_str);
else if (strcmp(m, "resources/read") == 0) response = handle_resources_read(server, id_str, params);
else if (strcmp(m, "prompts/list") == 0) response = handle_prompts_list(server, id_str);
else if (strcmp(m, "prompts/get") == 0) response = handle_prompts_get(server, id_str, params);
else response = create_error_response(id_str, JSONRPC_METHOD_NOT_FOUND, "Method not found");
cJSON_Delete(root);
return response;
}
static void *stdio_thread(void *arg)
{
mcp_server_t *server = (mcp_server_t *)arg;
char *line = NULL;
size_t len = 0;
ssize_t nread;
while (server->stdio_running) {
nread = getline(&line, &len, stdin);
if (nread <= 0) break;
if (line[nread - 1] == '\n') line[nread - 1] = '\0';
char *resp = mcp_server_handle_request(server, line);
if (resp) { fprintf(stdout, "%s\n", resp); fflush(stdout); free(resp); }
}
free(line);
return NULL;
}
int mcp_server_start_stdio(mcp_server_t *server)
{
if (!server || server->stdio_running) return -1;
server->stdio_running = true;
if (pthread_create(&server->stdio_thread, NULL, stdio_thread, server) != 0) {
server->stdio_running = false;
return -1;
}
return 0;
}
void mcp_server_stop_stdio(mcp_server_t *server)
{
if (!server || !server->stdio_running) return;
server->stdio_running = false;
pthread_cancel(server->stdio_thread);
pthread_join(server->stdio_thread, NULL);
}
* HTTP Transport — for remote MCP client integration via ws_server
*
* Unlike stdio transport which uses mcp_server's own tool registry,
* HTTP transport bridges to the main tool_registry (36+ real tools).
* This lets remote clients discover and call all device tools via standard MCP.
*
* Remote client config:
* {"name":"vela-watch","url":"http://<IP>:28789/mcp","transport":"http"}
* ══════════════════════════════════════════════════════════════ */
static const char *HTTP_TAG = "mcp_srv";
static void http_respond(int fd, int status, const char *status_text,
const char *body)
{
int body_len = body ? (int)strlen(body) : 0;
char hdr[256];
int hlen = snprintf(hdr, sizeof(hdr),
"HTTP/1.1 %d %s\r\n"
"Content-Type: application/json\r\n"
"Content-Length: %d\r\n"
"Connection: close\r\n\r\n",
status, status_text, body_len);
if (hlen >= (int)sizeof(hdr))
hlen = (int)sizeof(hdr) - 1;
send(fd, hdr, hlen, 0);
if (body && body_len > 0)
send(fd, body, body_len, 0);
}
static void http_jsonrpc_result(int fd, cJSON *id, cJSON *result)
{
cJSON *resp = cJSON_CreateObject();
if (!resp) { cJSON_Delete(result); return; }
cJSON_AddStringToObject(resp, "jsonrpc", "2.0");
cJSON_AddItemToObject(resp, "id", id ? cJSON_Duplicate(id, 1)
: cJSON_CreateNull());
cJSON_AddItemToObject(resp, "result", result);
char *json = cJSON_PrintUnformatted(resp);
cJSON_Delete(resp);
if (json) { http_respond(fd, 200, "OK", json); free(json); }
}
static void http_jsonrpc_error(int fd, cJSON *id, int code, const char *msg)
{
cJSON *resp = cJSON_CreateObject();
if (!resp) return;
cJSON_AddStringToObject(resp, "jsonrpc", "2.0");
cJSON_AddItemToObject(resp, "id", id ? cJSON_Duplicate(id, 1)
: cJSON_CreateNull());
cJSON *err = cJSON_AddObjectToObject(resp, "error");
if (err) {
cJSON_AddNumberToObject(err, "code", code);
cJSON_AddStringToObject(err, "message", msg);
}
char *json = cJSON_PrintUnformatted(resp);
cJSON_Delete(resp);
if (json) { http_respond(fd, 200, "OK", json); free(json); }
}
static void http_handle_initialize(int fd, cJSON *id)
{
cJSON *r = cJSON_CreateObject();
cJSON_AddStringToObject(r, "protocolVersion", "2024-11-05");
cJSON *caps = cJSON_AddObjectToObject(r, "capabilities");
if (caps) cJSON_AddObjectToObject(caps, "tools");
cJSON *info = cJSON_AddObjectToObject(r, "serverInfo");
if (info) {
cJSON_AddStringToObject(info, "name",
CONFIG_EXAMPLES_AI_AGENT_VELA_PROGNAME);
cJSON_AddStringToObject(info, "version", "1.0.0");
}
syslog(LOG_INFO, "[%s] initialize\n", HTTP_TAG);
http_jsonrpc_result(fd, id, r);
}
static void http_handle_tools_list(int fd, cJSON *id)
{
char *src_json = tool_registry_get_tools_json();
cJSON *r = cJSON_CreateObject();
cJSON *arr = cJSON_CreateArray();
if (src_json) {
cJSON *src = cJSON_Parse(src_json);
if (src) {
cJSON *t;
cJSON_ArrayForEach(t, src) {
cJSON *n = cJSON_GetObjectItem(t, "name");
cJSON *d = cJSON_GetObjectItem(t, "description");
cJSON *s = cJSON_GetObjectItem(t, "input_schema");
cJSON *tool = cJSON_CreateObject();
if (!tool) continue;
if (cJSON_IsString(n))
cJSON_AddStringToObject(tool, "name", n->valuestring);
if (cJSON_IsString(d))
cJSON_AddStringToObject(tool, "description", d->valuestring);
cJSON_AddItemToObject(tool, "inputSchema",
s ? cJSON_Duplicate(s, 1)
: cJSON_Parse("{\"type\":\"object\",\"properties\":{}}"));
cJSON_AddItemToArray(arr, tool);
}
cJSON_Delete(src);
}
free(src_json);
}
cJSON_AddItemToObject(r, "tools", arr);
syslog(LOG_INFO, "[%s] tools/list: %d\n", HTTP_TAG, cJSON_GetArraySize(arr));
http_jsonrpc_result(fd, id, r);
}
static void http_handle_tools_call(int fd, cJSON *id, cJSON *params)
{
cJSON *name = cJSON_GetObjectItem(params, "name");
cJSON *args = cJSON_GetObjectItem(params, "arguments");
if (!cJSON_IsString(name)) {
http_jsonrpc_error(fd, id, JSONRPC_INVALID_PARAMS, "missing params.name");
return;
}
char *args_str = args ? cJSON_PrintUnformatted(args) : strdup("{}");
if (!args_str) { http_jsonrpc_error(fd, id, JSONRPC_INTERNAL_ERROR, "OOM"); return; }
char *output = malloc(8192);
if (!output) { free(args_str); http_jsonrpc_error(fd, id, JSONRPC_INTERNAL_ERROR, "OOM"); return; }
output[0] = '\0';
syslog(LOG_INFO, "[%s] tools/call: %s\n", HTTP_TAG, name->valuestring);
int rc = tool_registry_execute(name->valuestring, args_str, output, 8192);
free(args_str);
cJSON *r = cJSON_CreateObject();
cJSON *content = cJSON_AddArrayToObject(r, "content");
if (content) {
cJSON *item = cJSON_CreateObject();
if (item) {
cJSON_AddStringToObject(item, "type", "text");
cJSON_AddStringToObject(item, "text",
(rc == 0 && output[0]) ? output : "tool execution failed");
cJSON_AddItemToArray(content, item);
}
}
if (rc != 0) cJSON_AddBoolToObject(r, "isError", 1);
free(output);
http_jsonrpc_result(fd, id, r);
}
static void http_dispatch(int fd, const char *body)
{
cJSON *req = cJSON_Parse(body);
if (!req) { http_jsonrpc_error(fd, NULL, JSONRPC_PARSE_ERROR, "parse error"); return; }
cJSON *method = cJSON_GetObjectItem(req, "method");
cJSON *id = cJSON_GetObjectItem(req, "id");
cJSON *params = cJSON_GetObjectItem(req, "params");
if (!cJSON_IsString(method)) {
http_jsonrpc_error(fd, id, JSONRPC_INVALID_REQUEST, "missing method");
cJSON_Delete(req);
return;
}
const char *m = method->valuestring;
if (strcmp(m, "initialize") == 0)
http_handle_initialize(fd, id);
else if (strcmp(m, "notifications/initialized") == 0)
http_respond(fd, 200, "OK", "");
else if (strcmp(m, "tools/list") == 0)
http_handle_tools_list(fd, id);
else if (strcmp(m, "tools/call") == 0)
http_handle_tools_call(fd, id, params);
else
http_jsonrpc_error(fd, id, JSONRPC_METHOD_NOT_FOUND, "method not found");
cJSON_Delete(req);
}
bool mcp_server_try_handle_http(int fd, const char *buf, int buf_len)
{
if (strncmp(buf, "POST ", 5) != 0) return false;
const char *pe = strchr(buf + 5, ' ');
if (!pe || (pe - buf - 5) != 4 || strncmp(buf + 5, "/mcp", 4) != 0)
return false;
const char *bs = strstr(buf, "\r\n\r\n");
if (!bs) { http_respond(fd, 400, "Bad Request", ""); return true; }
bs += 4;
int bib = buf_len - (int)(bs - buf);
char body[4096];
if (bib > 0 && bib < (int)sizeof(body))
memcpy(body, bs, bib);
else
bib = 0;
const char *cl = strcasestr(buf, "\r\nContent-Length: ");
int clen = cl ? atoi(cl + 18) : 0;
if (clen > (int)sizeof(body) - 1) clen = (int)sizeof(body) - 1;
while (bib < clen) {
int n = recv(fd, body + bib, clen - bib, 0);
if (n <= 0) break;
bib += n;
}
body[bib] = '\0';
http_dispatch(fd, body);
return true;
}