fix(mcp): return structured compact results

Tools advertise an object outputSchema, so strict MCP clients reject compact TOON responses that omit structuredContent.

Signed-off-by: wargloom <wargloom@gmail.com>
This commit is contained in:
wargloom
2026-07-12 13:55:08 +03:00
parent 8c9ac98e30
commit cabece18f1
2 changed files with 20 additions and 5 deletions
+15 -2
View File
@@ -258,17 +258,30 @@ char *cbm_mcp_text_result(const char *text, bool is_error) {
yyjson_mut_arr_add_val(content, item);
yyjson_mut_obj_add_val(doc, root, "content", content);
if (!is_error && text) {
bool has_structured_content = false;
if (text) {
yyjson_doc *structured_doc = yyjson_read(text, strlen(text), 0);
if (structured_doc) {
yyjson_val *structured_root = yyjson_doc_get_root(structured_doc);
if (yyjson_is_obj(structured_root)) {
yyjson_mut_val *structured = yyjson_val_mut_copy(doc, structured_root);
yyjson_mut_obj_add_val(doc, root, "structuredContent", structured);
if (structured) {
yyjson_mut_obj_add_val(doc, root, "structuredContent", structured);
has_structured_content = true;
}
}
yyjson_doc_free(structured_doc);
}
}
if (!has_structured_content) {
/* Every advertised MCP tool has an object outputSchema, so even compact
* TOON/plain-text and error results must carry a conforming structured
* object. Keep the text Content block above for model visibility and
* backwards compatibility. */
yyjson_mut_val *structured = yyjson_mut_obj(doc);
yyjson_mut_obj_add_str(doc, structured, is_error ? "error" : "text", text ? text : "");
yyjson_mut_obj_add_val(doc, root, "structuredContent", structured);
}
yyjson_mut_obj_add_bool(doc, root, "isError", is_error);
char *out = yy_doc_to_str(doc);
+5 -3
View File
@@ -448,10 +448,10 @@ TEST(mcp_text_result) {
PASS();
}
TEST(mcp_text_result_skips_structured_content_for_plain_text) {
TEST(mcp_text_result_wraps_plain_text_as_structured_content) {
char *json = cbm_mcp_text_result("plain text", false);
ASSERT_NOT_NULL(json);
ASSERT_NULL(strstr(json, "\"structuredContent\""));
ASSERT_NOT_NULL(strstr(json, "\"structuredContent\":{\"text\":\"plain text\"}"));
ASSERT_NOT_NULL(strstr(json, "\"isError\":false"));
free(json);
PASS();
@@ -470,6 +470,7 @@ TEST(mcp_cancel_matches_request_id) {
TEST(mcp_text_result_error) {
char *json = cbm_mcp_text_result("something failed", true);
ASSERT_NOT_NULL(json);
ASSERT_NOT_NULL(strstr(json, "\"structuredContent\":{\"error\":\"something failed\"}"));
ASSERT_NOT_NULL(strstr(json, "\"isError\":true"));
ASSERT_NOT_NULL(strstr(json, "something failed"));
free(json);
@@ -756,6 +757,7 @@ TEST(tool_search_graph_includes_node_properties) {
"\"arguments\":{\"project\":\"test-project\",\"label\":\"Function\","
"\"name_pattern\":\"HandleRequest\",\"limit\":5}}}");
ASSERT_NOT_NULL(resp);
ASSERT_NOT_NULL(strstr(resp, "\"structuredContent\":{\"text\":"));
char *inner = extract_text_content(resp);
ASSERT_NOT_NULL(inner);
ASSERT_NOT_NULL(strstr(inner, "results[")); /* TOON table header */
@@ -5611,7 +5613,7 @@ SUITE(mcp) {
RUN_TEST(mcp_ingest_traces_items_disallow_additional_properties_issue731);
RUN_TEST(mcp_get_architecture_aspects_schema_enum_pr560);
RUN_TEST(mcp_text_result);
RUN_TEST(mcp_text_result_skips_structured_content_for_plain_text);
RUN_TEST(mcp_text_result_wraps_plain_text_as_structured_content);
RUN_TEST(mcp_cancel_matches_request_id);
RUN_TEST(mcp_text_result_error);