cxxmcp 1.1.6
C++ MCP SDK
Loading...
Searching...
No Matches
tool.hpp
Go to the documentation of this file.
1// Copyright (c) 2025 [caomengxuan666]
2
3#pragma once
4
11
12#include <algorithm>
13#include <cctype>
14#include <cstdint>
15#include <optional>
16#include <string>
17#include <string_view>
18#include <unordered_map>
19#include <unordered_set>
20#include <utility>
21#include <vector>
22
30
31namespace mcp::protocol {
32
34enum class TaskSupport {
41};
42
46 std::optional<TaskSupport> task_support;
47
50 task_support = value;
51 return *this;
52 }
53
57 task_support = value;
58 return std::move(*this);
59 }
60};
61
62template <>
64 static constexpr bool defined = true;
65 static auto fields() {
66 return std::make_tuple(field("taskSupport", &ToolExecution::task_support));
67 }
68 static std::vector<std::string> known_keys() { return {"taskSupport"}; }
69};
71
72template <>
74 static void serialize(Json& json, const char* key,
75 const ToolExecution& value) {
76 json[key] = reflect_to_json(value);
77 }
78 static bool deserialize(const Json& json, const char* key,
79 ToolExecution& target) {
80 if (!json.contains(key)) {
81 return false;
82 }
83 auto result = reflect_from_json<ToolExecution>(json.at(key));
84 if (!result) {
85 return false;
86 }
87 target = std::move(*result);
88 return true;
89 }
90};
91
96 std::string type = "text";
98 std::string text;
100 Json data = Json::object();
102 std::string mime_type;
104 std::optional<ResourceContents> resource;
106 std::optional<Resource> resource_link;
108 Json annotations = Json::object();
110 std::optional<Json> meta;
112 Json extensions = Json::object();
113
115 static ContentBlock text_content(std::string value) {
116 ContentBlock block;
117 block.type = "text";
118 block.text = std::move(value);
119 return block;
120 }
121
123 static ContentBlock image(std::string base64_data, std::string mime_type) {
124 ContentBlock block;
125 block.type = "image";
126 block.data = std::move(base64_data);
127 block.mime_type = std::move(mime_type);
128 return block;
129 }
130
132 static ContentBlock audio(std::string base64_data, std::string mime_type) {
133 ContentBlock block;
134 block.type = "audio";
135 block.data = std::move(base64_data);
136 block.mime_type = std::move(mime_type);
137 return block;
138 }
139
142 ContentBlock block;
143 block.type = "resource";
144 block.resource = std::move(value);
145 return block;
146 }
147
150 ContentBlock block;
151 block.type = "resource_link";
152 block.resource_link = std::move(value);
153 return block;
154 }
155
157 bool is_text() const noexcept { return type == "text"; }
158
160 bool is_image() const noexcept { return type == "image"; }
161
163 bool is_audio() const noexcept { return type == "audio"; }
164
166 bool is_embedded_resource() const noexcept { return type == "resource"; }
167
169 bool is_resource_link() const noexcept { return type == "resource_link"; }
170
172 std::optional<std::string_view> as_text() const noexcept {
173 if (!is_text()) {
174 return std::nullopt;
175 }
176 return std::string_view(text);
177 }
178
180 std::optional<std::string_view> as_image_data() const {
181 if (!is_image() || !data.is_string()) {
182 return std::nullopt;
183 }
184 return std::string_view(data.get_ref<const std::string&>());
185 }
186
188 std::optional<std::string_view> as_audio_data() const {
189 if (!is_audio() || !data.is_string()) {
190 return std::nullopt;
191 }
192 return std::string_view(data.get_ref<const std::string&>());
193 }
194
196 const ResourceContents* as_embedded_resource() const noexcept {
197 if (!is_embedded_resource() || !resource.has_value()) {
198 return nullptr;
199 }
200 return &*resource;
201 }
202
204 const Resource* as_resource_link() const noexcept {
205 if (!is_resource_link() || !resource_link.has_value()) {
206 return nullptr;
207 }
208 return &*resource_link;
209 }
210};
211
215 std::string title;
217 std::string name;
219 std::string description;
221 Json input_schema = Json::object();
223 Json output_schema = Json::object();
227 bool streaming = false;
229 std::vector<Icon> icons;
231 std::optional<ToolExecution> execution;
233 std::optional<ToolAnnotations> tool_annotations;
235 Json annotations = Json::object();
237 std::optional<Json> meta;
239 Json extensions = Json::object();
240
242 TaskSupport task_support() const noexcept {
243 if (!execution.has_value() || !execution->task_support.has_value()) {
244 return TaskSupport::Forbidden;
245 }
246 return *execution->task_support;
247 }
248};
249
252 public:
253 explicit ToolDefinitionBuilder(std::string name) {
254 definition_.name = std::move(name);
255 }
256
257 ToolDefinitionBuilder& title(std::string value) {
258 definition_.title = std::move(value);
259 return *this;
260 }
261
262 ToolDefinitionBuilder& description(std::string value) {
263 definition_.description = std::move(value);
264 return *this;
265 }
266
267 ToolDefinitionBuilder& input_schema(Json schema) {
268 definition_.input_schema = std::move(schema);
269 return *this;
270 }
271
272 template <class T>
273 ToolDefinitionBuilder& input() {
274 return input_schema(schema_for<T>());
275 }
276
277 ToolDefinitionBuilder& output_schema(Json schema) {
278 definition_.output_schema = std::move(schema);
279 definition_.output_schema_present = true;
280 return *this;
281 }
282
283 template <class T>
284 ToolDefinitionBuilder& output() {
285 return output_schema(schema_for<T>());
286 }
287
288 ToolDefinitionBuilder& streaming(bool value = true) {
289 definition_.streaming = value;
290 return *this;
291 }
292
293 ToolDefinitionBuilder& icon(Icon value) {
294 definition_.icons.push_back(std::move(value));
295 return *this;
296 }
297
298 ToolDefinitionBuilder& task_support(TaskSupport value) {
299 if (!definition_.execution.has_value()) {
300 definition_.execution = ToolExecution{};
301 }
302 definition_.execution->task_support = value;
303 return *this;
304 }
305
306 ToolDefinitionBuilder& annotations(Json value) {
307 definition_.annotations = std::move(value);
308 return *this;
309 }
310
311 ToolDefinitionBuilder& tool_annotations(ToolAnnotations value) {
312 definition_.tool_annotations = std::move(value);
313 return *this;
314 }
315
316 ToolDefinitionBuilder& meta(Json value) {
317 definition_.meta = std::move(value);
318 return *this;
319 }
320
321 ToolDefinition build() const& { return definition_; }
322
323 ToolDefinition build() && { return std::move(definition_); }
324
325 private:
326 ToolDefinition definition_;
327};
328
330inline ToolDefinitionBuilder tool_definition(std::string name) {
331 return ToolDefinitionBuilder(std::move(name));
332}
333
335struct ToolCall {
337 std::string name;
339 Json arguments = Json::object();
341 std::optional<TaskRequestParameters> task;
343 std::optional<Json> meta;
345 std::optional<std::string> request_state;
347 std::optional<Json> input_responses;
349 Json extensions = Json::object();
350};
351
352template <>
354 static constexpr bool defined = true;
355 static auto fields() {
356 return std::make_tuple(
357 field("name", &ToolCall::name),
358 field("arguments", &ToolCall::arguments),
359 field("task", &ToolCall::task), field("_meta", &ToolCall::meta),
360 field("requestState", &ToolCall::request_state),
361 field("inputResponses", &ToolCall::input_responses),
363 {"name", "arguments", "task", "_meta", "requestState",
364 "inputResponses"}));
365 }
366 static std::vector<std::string> known_keys() {
367 return {"name", "arguments", "task",
368 "_meta", "requestState", "inputResponses"};
369 }
370};
371
375 std::vector<ToolDefinition> tools;
377 std::optional<std::string> next_cursor;
379 std::optional<Json> meta;
381 Json extensions = Json::object();
383 std::optional<std::int64_t> ttl_ms;
385 std::optional<std::string> cache_scope;
386};
387
391 std::vector<ContentBlock> content;
393 std::optional<Json> structured_content;
396 std::optional<bool> is_error;
398 std::optional<Json> meta;
401 std::optional<std::string> result_type;
403 std::optional<Json> input_requests;
405 std::optional<std::string> request_state;
407 Json extensions = Json::object();
408
410 bool is_error_result() const noexcept { return is_error.value_or(false); }
411
413 static ToolResult text(std::string value) {
414 ToolResult result;
415 result.content.push_back(ContentBlock::text_content(std::move(value)));
416 result.is_error = false;
417 return result;
418 }
419
421 static ToolResult error_text(std::string value) {
422 ToolResult result = text(std::move(value));
423 result.is_error = true;
424 return result;
425 }
426};
427
431inline core::Error tool_json_error(std::string message) {
432 return core::Error{
433 static_cast<int>(ErrorCode::InvalidRequest), std::move(message), {}};
434}
435
437inline std::string_view task_support_to_string(TaskSupport support) noexcept {
438 switch (support) {
439 case TaskSupport::Forbidden:
440 return "forbidden";
441 case TaskSupport::Optional:
442 return "optional";
443 case TaskSupport::Required:
444 return "required";
445 }
446 return "forbidden";
447}
448
450inline std::optional<TaskSupport> task_support_from_string(
451 std::string_view value) noexcept {
452 if (value == "forbidden") {
453 return TaskSupport::Forbidden;
454 }
455 if (value == "optional") {
456 return TaskSupport::Optional;
457 }
458 if (value == "required") {
459 return TaskSupport::Required;
460 }
461 return std::nullopt;
462}
463
464template <>
466 static void serialize(Json& json, const char* key, TaskSupport value) {
467 json[key] = std::string(task_support_to_string(value));
468 }
469 static bool deserialize(const Json& json, const char* key,
470 TaskSupport& target) {
471 if (!json.contains(key) || !json.at(key).is_string()) {
472 return false;
473 }
474 auto val =
475 task_support_from_string(json.at(key).get_ref<const std::string&>());
476 if (!val.has_value()) {
477 return false;
478 }
479 target = *val;
480 return true;
481 }
482};
483
485inline bool is_valid_base64(std::string_view value) noexcept {
486 if (value.empty()) {
487 return true;
488 }
489 if (value.size() % 4 != 0) {
490 return false;
491 }
492
493 std::size_t padding = 0;
494 if (value.back() == '=') {
495 ++padding;
496 if (value.size() >= 2 && value[value.size() - 2] == '=') {
497 ++padding;
498 }
499 }
500
501 for (std::size_t i = 0; i < value.size(); ++i) {
502 const unsigned char c = static_cast<unsigned char>(value[i]);
503 const bool is_base64_char = (c >= 'A' && c <= 'Z') ||
504 (c >= 'a' && c <= 'z') ||
505 (c >= '0' && c <= '9') || c == '+' || c == '/';
506 if (is_base64_char) {
507 continue;
508 }
509 if (c == '=' && i >= value.size() - padding) {
510 continue;
511 }
512 return false;
513 }
514
515 return true;
516}
517
519inline Json tool_execution_to_json(const ToolExecution& execution) {
520 return reflect_to_json(execution);
521}
522
526 return reflect_from_json<ToolExecution>(json);
527}
528
531 Json json = Json::object();
532 json["type"] = block.type;
533 if (block.type == "resource" && block.resource.has_value()) {
534 json["resource"] = resource_contents_to_json(*block.resource);
535 } else if (block.type == "resource_link" && block.resource_link.has_value()) {
536 json = resource_to_json(*block.resource_link);
537 json["type"] = block.type;
538 } else {
539 if (!block.text.empty() || block.type == "text") {
540 json["text"] = block.text;
541 }
542 if (((block.type == "image" || block.type == "audio") &&
543 block.data.is_string()) ||
544 !block.data.empty()) {
545 json["data"] = block.data;
546 }
547 if (!block.mime_type.empty()) {
548 json["mimeType"] = block.mime_type;
549 }
550 }
551 if (!block.annotations.empty()) {
552 json["annotations"] = block.annotations;
553 }
554 if (block.meta.has_value()) {
555 json["_meta"] = *block.meta;
556 }
558 return json;
559}
560
563 const Json& json, std::string_view member, std::string message) {
564 const std::string key(member);
565 if (!json.contains(key) || !json.at(key).is_string()) {
566 return mcp::core::unexpected(tool_json_error(std::move(message)));
567 }
568 return json.at(key).get<std::string>();
569}
570
574 if (!json.is_object()) {
575 return mcp::core::unexpected(
576 tool_json_error("content block must be an object"));
577 }
578
579 ContentBlock block;
580 if (!json.contains("type") || !json.at("type").is_string()) {
581 return mcp::core::unexpected(
582 tool_json_error("content block requires a string type"));
583 }
584 block.type = json.at("type").get<std::string>();
585
586 if (block.type == "image" || block.type == "audio") {
587 const auto data = required_content_string(
588 json, "data", block.type + " content block requires string data");
589 if (!data) {
590 return mcp::core::unexpected(data.error());
591 }
592 const auto mime_type = required_content_string(
593 json, "mimeType",
594 block.type + " content block requires string mimeType");
595 if (!mime_type) {
596 return mcp::core::unexpected(mime_type.error());
597 }
598 if (!is_valid_base64(*data)) {
599 return mcp::core::unexpected(
600 tool_json_error(block.type + " content block data must be base64"));
601 }
602 block.data = *data;
603 block.mime_type = *mime_type;
604 } else if (block.type == "resource") {
605 if (!json.contains("resource")) {
606 return mcp::core::unexpected(
607 tool_json_error("resource content block requires resource"));
608 }
609 const auto resource = resource_contents_from_json(json.at("resource"));
610 if (!resource) {
611 return mcp::core::unexpected(resource.error());
612 }
613 block.resource = *resource;
614 } else if (block.type == "resource_link") {
615 const auto resource = resource_from_json(json);
616 if (!resource) {
617 return mcp::core::unexpected(resource.error());
618 }
619 block.resource_link = *resource;
620 } else if (block.type == "text") {
621 const auto text = required_content_string(
622 json, "text", "text content block requires string text");
623 if (!text) {
624 return mcp::core::unexpected(text.error());
625 }
626 block.text = *text;
627 } else {
628 return mcp::core::unexpected(
629 tool_json_error("content block type is not supported"));
630 }
631
632 if (json.contains("annotations")) {
633 const auto parsed = annotations_from_json(json.at("annotations"));
634 if (!parsed) {
635 return mcp::core::unexpected(parsed.error());
636 }
637 block.annotations = annotations_to_json(*parsed);
638 }
639 if (json.contains("_meta")) {
640 if (!json.at("_meta").is_object()) {
641 return mcp::core::unexpected(
642 tool_json_error("content block _meta must be an object"));
643 }
644 block.meta = json.at("_meta");
645 }
647 json,
648 {"type", "text", "data", "mimeType", "resource", "uri", "name", "title",
649 "description", "mimeType", "size", "icons", "annotations", "_meta"});
650
651 return block;
652}
653
655inline Json tool_definition_to_json(const ToolDefinition& definition) {
656 Json json = Json::object();
657 if (!definition.title.empty()) {
658 json["title"] = definition.title;
659 }
660 json["name"] = definition.name;
661 if (!definition.description.empty()) {
662 json["description"] = definition.description;
663 }
664 json["inputSchema"] = definition.input_schema;
665 if (definition.output_schema_present || !definition.output_schema.empty()) {
666 json["outputSchema"] = definition.output_schema;
667 }
668 if (definition.streaming) {
669 json["streaming"] = definition.streaming;
670 }
671 if (!definition.icons.empty()) {
672 json["icons"] = Json::array();
673 for (const auto& icon : definition.icons) {
674 json["icons"].push_back(icon_to_json(icon));
675 }
676 }
677 if (definition.execution.has_value()) {
678 json["execution"] = tool_execution_to_json(*definition.execution);
679 }
680 if (definition.tool_annotations.has_value()) {
681 json["annotations"] =
683 } else if (!definition.annotations.empty()) {
684 json["annotations"] = definition.annotations;
685 }
686 if (definition.meta.has_value()) {
687 json["_meta"] = *definition.meta;
688 }
689 append_json_extensions(json, definition.extensions);
690 return json;
691}
692
696 const Json& json) {
697 if (!json.is_object()) {
698 return mcp::core::unexpected(
699 tool_json_error("tool definition must be an object"));
700 }
701
702 ToolDefinition definition;
703 if (json.contains("title")) {
704 if (!json.at("title").is_string()) {
705 return mcp::core::unexpected(
706 tool_json_error("tool definition title must be a string"));
707 }
708 definition.title = json.at("title").get<std::string>();
709 }
710 if (!json.contains("name") || !json.at("name").is_string()) {
711 return mcp::core::unexpected(
712 tool_json_error("tool definition requires a string name"));
713 }
714 definition.name = json.at("name").get<std::string>();
715
716 if (json.contains("description")) {
717 if (!json.at("description").is_string()) {
718 return mcp::core::unexpected(
719 tool_json_error("tool definition description must be a string"));
720 }
721 definition.description = json.at("description").get<std::string>();
722 }
723
724 if (!json.contains("inputSchema") || !json.at("inputSchema").is_object()) {
725 return mcp::core::unexpected(
726 tool_json_error("tool definition requires object inputSchema"));
727 }
728 definition.input_schema = json.at("inputSchema");
729
730 if (json.contains("outputSchema")) {
731 if (!json.at("outputSchema").is_object()) {
732 return mcp::core::unexpected(
733 tool_json_error("tool definition outputSchema must be an object"));
734 }
735 definition.output_schema = json.at("outputSchema");
736 definition.output_schema_present = true;
737 }
738
739 if (json.contains("streaming")) {
740 if (!json.at("streaming").is_boolean()) {
741 return mcp::core::unexpected(
742 tool_json_error("tool definition streaming must be a boolean"));
743 }
744 definition.streaming = json.at("streaming").get<bool>();
745 }
746
747 if (json.contains("icons")) {
748 if (!json.at("icons").is_array()) {
749 return mcp::core::unexpected(
750 tool_json_error("tool definition icons must be an array"));
751 }
752 for (const auto& item : json.at("icons")) {
753 const auto icon = icon_from_json(item);
754 if (!icon.has_value()) {
755 return mcp::core::unexpected(
756 tool_json_error("tool definition icon is invalid"));
757 }
758 definition.icons.push_back(*icon);
759 }
760 }
761
762 if (json.contains("execution")) {
763 const auto execution = tool_execution_from_json(json.at("execution"));
764 if (!execution) {
765 return mcp::core::unexpected(execution.error());
766 }
767 definition.execution = *execution;
768 }
769
770 if (json.contains("annotations")) {
771 if (!json.at("annotations").is_object()) {
772 return mcp::core::unexpected(
773 tool_json_error("tool definition annotations must be an object"));
774 }
775 const auto parsed = tool_annotations_from_json(json.at("annotations"));
776 if (parsed) {
777 definition.tool_annotations = *parsed;
778 }
779 definition.annotations = json.at("annotations");
780 }
781
782 if (json.contains("_meta")) {
783 if (!json.at("_meta").is_object()) {
784 return mcp::core::unexpected(
785 tool_json_error("tool definition _meta must be an object"));
786 }
787 definition.meta = json.at("_meta");
788 }
790 json, {"title", "name", "description", "inputSchema", "outputSchema",
791 "streaming", "icons", "execution", "annotations", "_meta"});
792
793 return definition;
794}
795
798 Json json = Json::object();
799 json["tools"] = Json::array();
800 for (const auto& tool : result.tools) {
801 json["tools"].push_back(tool_definition_to_json(tool));
802 }
803 if (result.next_cursor.has_value()) {
804 json["nextCursor"] = *result.next_cursor;
805 }
806 if (result.meta.has_value()) {
807 json["_meta"] = *result.meta;
808 }
809 if (result.ttl_ms.has_value()) {
810 json["ttlMs"] = *result.ttl_ms;
811 }
812 if (result.cache_scope.has_value()) {
813 json["cacheScope"] = *result.cache_scope;
814 }
816 return json;
817}
818
822 const Json& json) {
823 if (!json.is_object()) {
824 return mcp::core::unexpected(
825 tool_json_error("tools/list result must be an object"));
826 }
827 if (!json.contains("tools") || !json.at("tools").is_array()) {
828 return mcp::core::unexpected(
829 tool_json_error("tools/list result requires a tools array"));
830 }
831
832 ToolsListResult result;
833 for (const auto& item : json.at("tools")) {
834 const auto tool = tool_definition_from_json(item);
835 if (!tool) {
836 return mcp::core::unexpected(tool.error());
837 }
838 result.tools.push_back(*tool);
839 }
840 if (json.contains("nextCursor")) {
841 if (!json.at("nextCursor").is_string()) {
842 return mcp::core::unexpected(
843 tool_json_error("tools/list nextCursor must be a string"));
844 }
845 result.next_cursor = json.at("nextCursor").get<std::string>();
846 }
847 if (json.contains("_meta")) {
848 if (!json.at("_meta").is_object()) {
849 return mcp::core::unexpected(
850 tool_json_error("tools/list result _meta must be an object"));
851 }
852 result.meta = json.at("_meta");
853 }
854 result.extensions =
855 collect_json_extensions(json, {"tools", "nextCursor", "_meta"});
856 return result;
857}
858
860inline Json tool_call_to_json(const ToolCall& call) {
861 Json json = Json::object();
862 json["name"] = call.name;
863 if (!call.arguments.empty()) {
864 json["arguments"] = call.arguments;
865 }
866 if (call.task.has_value()) {
867 json["task"] = task_request_parameters_to_json(*call.task);
868 }
869 if (call.meta.has_value()) {
870 json["_meta"] = *call.meta;
871 }
872 if (call.request_state.has_value()) {
873 json["requestState"] = *call.request_state;
874 }
875 if (call.input_responses.has_value()) {
876 json["inputResponses"] = *call.input_responses;
877 }
879 return json;
880}
881
885 if (!json.is_object()) {
886 return mcp::core::unexpected(
887 tool_json_error("tools/call params must be an object"));
888 }
889 if (!json.contains("name") || !json.at("name").is_string()) {
890 return mcp::core::unexpected(
891 tool_json_error("tools/call params require a string name"));
892 }
893
894 ToolCall call;
895 call.name = json.at("name").get<std::string>();
896 if (json.contains("arguments")) {
897 if (!json.at("arguments").is_object()) {
898 return mcp::core::unexpected(
899 tool_json_error("tools/call arguments must be an object"));
900 }
901 call.arguments = json.at("arguments");
902 }
903 if (json.contains("task")) {
904 const auto task = task_request_parameters_from_json(json.at("task"));
905 if (!task) {
906 return mcp::core::unexpected(task.error());
907 }
908 call.task = *task;
909 }
910 if (json.contains("_meta")) {
911 if (!json.at("_meta").is_object()) {
912 return mcp::core::unexpected(
913 tool_json_error("tools/call _meta must be an object"));
914 }
915 call.meta = json.at("_meta");
916 }
917 if (json.contains("requestState")) {
918 call.request_state = json.at("requestState").get<std::string>();
919 }
920 if (json.contains("inputResponses")) {
921 call.input_responses = json.at("inputResponses");
922 }
924 json,
925 {"name", "arguments", "task", "_meta", "requestState", "inputResponses"});
926 return call;
927}
928
930inline Json tool_result_to_json(const ToolResult& result) {
931 Json json = Json::object();
932 if (result.result_type.has_value()) {
933 json["resultType"] = *result.result_type;
934 }
935 if (!result.content.empty()) {
936 json["content"] = Json::array();
937 for (const auto& block : result.content) {
938 json["content"].push_back(content_block_to_json(block));
939 }
940 }
941 if (result.structured_content.has_value()) {
942 json["structuredContent"] = *result.structured_content;
943 }
944 if (result.is_error.has_value()) {
945 json["isError"] = *result.is_error;
946 }
947 if (result.meta.has_value()) {
948 json["_meta"] = *result.meta;
949 }
950 if (result.input_requests.has_value()) {
951 json["inputRequests"] = *result.input_requests;
952 }
953 if (result.request_state.has_value()) {
954 json["requestState"] = *result.request_state;
955 }
957 return json;
958}
959
963 if (!json.is_object()) {
964 return mcp::core::unexpected(
965 tool_json_error("tool result must be an object"));
966 }
967 if (!json.contains("content") && !json.contains("structuredContent") &&
968 !json.contains("isError") && !json.contains("_meta") &&
969 !json.contains("resultType") && !json.contains("inputRequests") &&
970 !json.contains("requestState")) {
971 return mcp::core::unexpected(tool_json_error(
972 "tool result requires content, structuredContent, isError, _meta, "
973 "resultType, inputRequests, or requestState"));
974 }
975
976 ToolResult result;
977 if (json.contains("resultType")) {
978 result.result_type = json.at("resultType").get<std::string>();
979 }
980 if (json.contains("content")) {
981 if (!json.at("content").is_array()) {
982 return mcp::core::unexpected(
983 tool_json_error("tool result content must be an array"));
984 }
985 for (const auto& item : json.at("content")) {
986 const auto block = content_block_from_json(item);
987 if (!block) {
988 return mcp::core::unexpected(block.error());
989 }
990 result.content.push_back(*block);
991 }
992 }
993
994 if (json.contains("structuredContent")) {
995 result.structured_content = json.at("structuredContent");
996 }
997
998 if (json.contains("isError")) {
999 if (!json.at("isError").is_boolean()) {
1000 return mcp::core::unexpected(
1001 tool_json_error("tool result isError must be a boolean"));
1002 }
1003 result.is_error = json.at("isError").get<bool>();
1004 }
1005
1006 if (json.contains("_meta")) {
1007 if (!json.at("_meta").is_object()) {
1008 return mcp::core::unexpected(
1009 tool_json_error("tool result _meta must be an object"));
1010 }
1011 result.meta = json.at("_meta");
1012 }
1013 if (json.contains("inputRequests")) {
1014 result.input_requests = json.at("inputRequests");
1015 }
1016 if (json.contains("requestState")) {
1017 result.request_state = json.at("requestState").get<std::string>();
1018 }
1020 json, {"content", "structuredContent", "isError", "_meta", "resultType",
1021 "inputRequests", "requestState"});
1022
1023 return result;
1024}
1025
1026// ── SEP-2243: x-mcp-header support ──────────────────────────────────────────
1027
1030 std::string header_name; // x-mcp-header value, e.g. "Region"
1031 std::string param_name; // property key, e.g. "region"
1032 std::string type; // JSON Schema type: "string", "number", etc.
1033};
1034
1036inline std::vector<XHeaderEntry> extract_x_mcp_headers(
1037 const Json& input_schema) {
1038 std::vector<XHeaderEntry> result;
1039 if (!input_schema.is_object() || !input_schema.contains("properties") ||
1040 !input_schema.at("properties").is_object()) {
1041 return result;
1042 }
1043 for (auto& [key, prop] : input_schema.at("properties").items()) {
1044 if (!prop.is_object() || !prop.contains("x-mcp-header") ||
1045 !prop.at("x-mcp-header").is_string()) {
1046 continue;
1047 }
1048 std::string type_str;
1049 if (prop.contains("type") && prop.at("type").is_string()) {
1050 type_str = prop.at("type").get<std::string>();
1051 }
1052 result.push_back({prop.at("x-mcp-header").get<std::string>(),
1053 std::string(key), std::move(type_str)});
1054 }
1055 return result;
1056}
1057
1060inline bool validate_tool_x_headers(const std::vector<XHeaderEntry>& entries) {
1061 std::unordered_set<std::string> seen_lower;
1062 for (const auto& entry : entries) {
1063 if (entry.header_name.empty()) return false;
1064 for (char ch : entry.header_name) {
1065 const auto c = static_cast<unsigned char>(ch);
1066 if (c < 0x21 || c > 0x7E || c == ':') return false;
1067 }
1068 if (entry.type == "object" || entry.type == "array" ||
1069 entry.type == "null" || entry.type.empty()) {
1070 return false;
1071 }
1072 std::string lower = entry.header_name;
1073 std::transform(
1074 lower.begin(), lower.end(), lower.begin(),
1075 [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
1076 if (!seen_lower.insert(lower).second) return false;
1077 }
1078 return true;
1079}
1080
1082inline bool needs_base64_encoding(std::string_view value) {
1083 for (const char ch : value) {
1084 const auto c = static_cast<unsigned char>(ch);
1085 if (c < 0x20 || c > 0x7E) return true;
1086 }
1087 if (!value.empty() && (value.front() == ' ' || value.back() == ' ')) {
1088 return true;
1089 }
1090 return false;
1091}
1092
1094inline std::string base64_encode(std::string_view input) {
1095 static constexpr char kAlphabet[] =
1096 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1097 std::string result;
1098 result.reserve(((input.size() + 2) / 3) * 4);
1099 const auto* data = reinterpret_cast<const unsigned char*>(input.data());
1100 std::size_t i = 0;
1101 while (i + 2 < input.size()) {
1102 result.push_back(kAlphabet[(data[i] >> 2) & 0x3F]);
1103 result.push_back(
1104 kAlphabet[((data[i] & 0x03) << 4) | ((data[i + 1] >> 4) & 0x0F)]);
1105 result.push_back(
1106 kAlphabet[((data[i + 1] & 0x0F) << 2) | ((data[i + 2] >> 6) & 0x03)]);
1107 result.push_back(kAlphabet[data[i + 2] & 0x3F]);
1108 i += 3;
1109 }
1110 if (i + 1 == input.size()) {
1111 result.push_back(kAlphabet[(data[i] >> 2) & 0x3F]);
1112 result.push_back(kAlphabet[(data[i] & 0x03) << 4]);
1113 result.push_back('=');
1114 result.push_back('=');
1115 } else if (i + 2 == input.size()) {
1116 result.push_back(kAlphabet[(data[i] >> 2) & 0x3F]);
1117 result.push_back(
1118 kAlphabet[((data[i] & 0x03) << 4) | ((data[i + 1] >> 4) & 0x0F)]);
1119 result.push_back(kAlphabet[(data[i + 1] & 0x0F) << 2]);
1120 result.push_back('=');
1121 }
1122 return result;
1123}
1124
1126inline std::string encode_header_value(std::string_view value) {
1127 if (needs_base64_encoding(value)) {
1128 return "=?base64?" + base64_encode(value) + "?=";
1129 }
1130 return std::string(value);
1131}
1132
1134inline std::string number_to_header_string(const Json& value) {
1135 if (value.is_number_integer()) {
1136 return std::to_string(value.get<std::int64_t>());
1137 }
1138 return value.dump();
1139}
1140
1145inline std::unordered_map<std::string, std::string> build_tool_param_headers(
1146 const Json& arguments, const std::vector<XHeaderEntry>& entries) {
1147 std::unordered_map<std::string, std::string> headers;
1148 for (const auto& entry : entries) {
1149 if (!arguments.contains(entry.param_name)) continue;
1150 const auto& value = arguments.at(entry.param_name);
1151 if (value.is_null()) continue;
1152 std::string header_key = "Mcp-Param-" + entry.header_name;
1153 if (value.is_boolean()) {
1154 headers[header_key] = value.get<bool>() ? "true" : "false";
1155 } else if (value.is_number()) {
1156 headers[header_key] = number_to_header_string(value);
1157 } else if (value.is_string()) {
1158 headers[header_key] = encode_header_value(value.get<std::string>());
1159 }
1160 }
1161 return headers;
1162}
1163
1164} // namespace mcp::protocol
Typed annotations for content blocks, tool definitions, and other protocol objects.
Json tool_annotations_to_json(const ToolAnnotations &annotations)
Serializes a ToolAnnotations struct to JSON.
Definition annotations.hpp:71
core::Result< Annotations > annotations_from_json(const Json &json)
Parses an Annotations struct from JSON.
Definition annotations.hpp:213
core::Result< ToolAnnotations > tool_annotations_from_json(const Json &json)
Parses a ToolAnnotations struct from JSON.
Definition annotations.hpp:102
Json annotations_to_json(const Annotations &annotations)
Serializes an Annotations struct to JSON.
Definition annotations.hpp:181
Fluent builder for advertised MCP tool metadata.
Definition tool.hpp:251
Shared JSON, JSON-RPC, error, cancellation, and progress model types.
void append_json_extensions(Json &json, const Json &extensions)
Flattens extension members into a JSON object without overwriting typed fields.
Definition types.hpp:358
nlohmann::json Json
JSON value type used by all protocol DTOs.
Definition types.hpp:28
Json collect_json_extensions(const Json &json, std::initializer_list< std::string_view > known_keys)
Collects unknown object members so typed DTOs can preserve future protocol fields and vendor extensio...
Definition types.hpp:320
#define CXXMCP_REFLECT_CHECK(Struct, expected_count)
Validates that a Reflect<> specialization covers the expected number of fields.
Definition reflect.hpp:1008
constexpr FieldDescriptor< Struct, Field > field(const char *wire_name, Field Struct::*pointer)
Creates a FieldDescriptor with wire name and pointer-to-member.
Definition reflect.hpp:75
ExtensionsField< Struct > extensions_field(Json Struct::*pointer, std::vector< std::string > own_keys)
Creates an ExtensionsField descriptor.
Definition reflect.hpp:90
Json reflect_to_json(const T &obj)
Serializes a DTO to JSON using its Reflect<T> trait.
Definition reflect.hpp:701
Resource listing, template, subscription, and read payloads.
Json resource_contents_to_json(const ResourceContents &contents)
Serializes resource contents.
Definition resource.hpp:788
core::Result< ResourceContents > resource_contents_from_json(const Json &json)
Parses resource contents.
Definition resource.hpp:795
core::Result< Resource > resource_from_json(const Json &json)
Parses a resource descriptor.
Definition resource.hpp:374
Json resource_to_json(const Resource &resource)
Serializes a resource descriptor.
Definition resource.hpp:340
Shared result and error primitives used by the public cxxmcp SDK.
tl::expected< T, Error > Result
Alias for the SDK result type.
Definition result.hpp:64
@ Required
Require at least one tool use.
Small JSON Schema builders for MCP tool and elicitation metadata.
Structured error returned by fallible SDK operations.
Definition result.hpp:35
A single content item returned by a tool or embedded in prompts.
Definition tool.hpp:93
const ResourceContents * as_embedded_resource() const noexcept
Returns embedded resource contents when present.
Definition tool.hpp:196
std::optional< Json > meta
Optional _meta extension object preserved on the wire.
Definition tool.hpp:110
std::string type
Content kind.
Definition tool.hpp:96
Json data
Base64 payload for image/audio blocks or extension data for custom blocks.
Definition tool.hpp:100
bool is_text() const noexcept
Returns true when this block is a text block.
Definition tool.hpp:157
static ContentBlock embedded_resource(ResourceContents value)
Creates an embedded resource content block.
Definition tool.hpp:141
std::optional< std::string_view > as_text() const noexcept
Returns the text payload when this is a text block.
Definition tool.hpp:172
std::optional< Resource > resource_link
Resource descriptor for resource_link blocks.
Definition tool.hpp:106
std::optional< std::string_view > as_image_data() const
Returns base64 image data when this is an image block.
Definition tool.hpp:180
static ContentBlock audio(std::string base64_data, std::string mime_type)
Creates an audio content block with base64 data.
Definition tool.hpp:132
Json extensions
Unknown JSON members preserved for forward-compatible round trips.
Definition tool.hpp:112
std::optional< std::string_view > as_audio_data() const
Returns base64 audio data when this is an audio block.
Definition tool.hpp:188
static ContentBlock image(std::string base64_data, std::string mime_type)
Creates an image content block with base64 data.
Definition tool.hpp:123
static ContentBlock resource_link_content(Resource value)
Creates a resource link content block.
Definition tool.hpp:149
bool is_image() const noexcept
Returns true when this block is an image block.
Definition tool.hpp:160
const Resource * as_resource_link() const noexcept
Returns the linked resource descriptor when present.
Definition tool.hpp:204
std::string text
Text payload for text blocks.
Definition tool.hpp:98
bool is_embedded_resource() const noexcept
Returns true when this block embeds resource contents.
Definition tool.hpp:166
bool is_resource_link() const noexcept
Returns true when this block links to a resource descriptor.
Definition tool.hpp:169
bool is_audio() const noexcept
Returns true when this block is an audio block.
Definition tool.hpp:163
static ContentBlock text_content(std::string value)
Creates a text content block.
Definition tool.hpp:115
Json annotations
Optional annotations for model or client presentation.
Definition tool.hpp:108
std::optional< ResourceContents > resource
Embedded resource contents for resource blocks.
Definition tool.hpp:104
std::string mime_type
MIME type for image/audio blocks.
Definition tool.hpp:102
Icon descriptor used by tools, resources, resource templates, and prompts.
Definition types.hpp:160
Type-specific serialization and deserialization logic.
Definition reflect.hpp:246
Primary template.
Definition reflect.hpp:199
One content part returned by resources/read.
Definition resource.hpp:250
Concrete resource advertised by resources/list.
Definition resource.hpp:28
Typed representation of MCP tool annotations.
Definition annotations.hpp:53
Parameters for tools/call.
Definition tool.hpp:335
std::optional< Json > input_responses
Client-provided responses to server input requests (MRTR).
Definition tool.hpp:347
Json arguments
JSON argument object validated against the tool input schema.
Definition tool.hpp:339
std::optional< Json > meta
Optional _meta extension object preserved on the wire.
Definition tool.hpp:343
std::string name
Tool name matching a ToolDefinition.
Definition tool.hpp:337
Json extensions
Unknown JSON members preserved for forward-compatible round trips.
Definition tool.hpp:349
std::optional< std::string > request_state
Opaque request state echoed back on MRTR retries (SEP-2322).
Definition tool.hpp:345
std::optional< TaskRequestParameters > task
Optional task request parameters when asynchronous execution is desired.
Definition tool.hpp:341
Metadata describing a callable MCP tool.
Definition tool.hpp:213
Json input_schema
JSON Schema object describing accepted arguments.
Definition tool.hpp:221
TaskSupport task_support() const noexcept
Returns the effective task support mode for this tool.
Definition tool.hpp:242
std::optional< ToolExecution > execution
Optional execution configuration including task support mode.
Definition tool.hpp:231
Json output_schema
Optional JSON Schema object describing structured result content.
Definition tool.hpp:223
std::string title
Optional human-readable display title.
Definition tool.hpp:215
Json annotations
Optional raw annotations preserved for forward-compatible round trips.
Definition tool.hpp:235
std::vector< Icon > icons
Optional icon descriptors for client presentation.
Definition tool.hpp:229
bool output_schema_present
Whether output_schema was explicitly present on the wire or configured.
Definition tool.hpp:225
std::string description
Human-readable tool description.
Definition tool.hpp:219
std::string name
Stable protocol identifier used by tools/call.
Definition tool.hpp:217
Json extensions
Unknown JSON members preserved for forward-compatible round trips.
Definition tool.hpp:239
std::optional< ToolAnnotations > tool_annotations
Optional typed tool annotations for model or client presentation.
Definition tool.hpp:233
std::optional< Json > meta
Optional _meta extension object preserved on the wire.
Definition tool.hpp:237
bool streaming
Whether the tool may stream partial results outside a single response.
Definition tool.hpp:227
Execution configuration advertised with a tool definition.
Definition tool.hpp:44
ToolExecution & with_task_support(TaskSupport value) &
Sets the optional task support mode on an lvalue execution object.
Definition tool.hpp:49
std::optional< TaskSupport > task_support
Optional task support mode. Missing means forbidden by default.
Definition tool.hpp:46
ToolExecution && with_task_support(TaskSupport value) &&
Sets the optional task support mode while preserving fluent temporary use.
Definition tool.hpp:56
Result object for tools/call.
Definition tool.hpp:389
std::optional< Json > meta
Optional _meta extension object preserved on the wire.
Definition tool.hpp:398
std::vector< ContentBlock > content
Ordered user/model-visible content returned by the tool.
Definition tool.hpp:391
static ToolResult error_text(std::string value)
Creates an error text-only tool result.
Definition tool.hpp:421
static ToolResult text(std::string value)
Creates a successful text-only tool result.
Definition tool.hpp:413
std::optional< bool > is_error
Optional domain-level error signal.
Definition tool.hpp:396
std::optional< Json > structured_content
Optional machine-readable result matching the tool output schema.
Definition tool.hpp:393
std::optional< std::string > result_type
Result type discriminator.
Definition tool.hpp:401
std::optional< std::string > request_state
Opaque request state for MRTR retries (SEP-2322).
Definition tool.hpp:405
Json extensions
Unknown JSON members preserved for forward-compatible round trips.
Definition tool.hpp:407
bool is_error_result() const noexcept
Convenience predicate treating a missing signal as success.
Definition tool.hpp:410
std::optional< Json > input_requests
Server input requests for MRTR (SEP-2322).
Definition tool.hpp:403
Result object for tools/list.
Definition tool.hpp:373
std::optional< std::int64_t > ttl_ms
Cache time-to-live hint in milliseconds (SEP-2549).
Definition tool.hpp:383
std::optional< Json > meta
Optional _meta extension object preserved on the wire.
Definition tool.hpp:379
std::vector< ToolDefinition > tools
Tools available to the caller.
Definition tool.hpp:375
std::optional< std::string > cache_scope
Cache scope hint: "public" or "private" (SEP-2549).
Definition tool.hpp:385
std::optional< std::string > next_cursor
Optional cursor for retrieving the next page.
Definition tool.hpp:377
Json extensions
Unknown JSON members preserved for forward-compatible round trips.
Definition tool.hpp:381
One x-mcp-header annotation extracted from a tool input schema.
Definition tool.hpp:1029
Asynchronous task status and task-management payloads.
Json task_request_parameters_to_json(const TaskRequestParameters &parameters)
Serializes optional task request parameters.
Definition task.hpp:319
core::Result< TaskRequestParameters > task_request_parameters_from_json(const Json &json)
Parses optional task request parameters.
Definition task.hpp:326
core::Result< ToolDefinition > tool_definition_from_json(const Json &json)
Parses a tool definition from JSON.
Definition tool.hpp:695
std::unordered_map< std::string, std::string > build_tool_param_headers(const Json &arguments, const std::vector< XHeaderEntry > &entries)
Builds Mcp-Param-* transport headers from tool arguments and schema.
Definition tool.hpp:1145
Json tool_call_to_json(const ToolCall &call)
Serializes tools/call params.
Definition tool.hpp:860
std::string_view task_support_to_string(TaskSupport support) noexcept
Converts a task support mode to the lowercase wire value.
Definition tool.hpp:437
Json tool_execution_to_json(const ToolExecution &execution)
Serializes tool execution configuration.
Definition tool.hpp:519
core::Result< ToolResult > tool_result_from_json(const Json &json)
Parses a tool result.
Definition tool.hpp:962
Json tool_result_to_json(const ToolResult &result)
Serializes a tool result.
Definition tool.hpp:930
core::Result< ToolExecution > tool_execution_from_json(const Json &json)
Parses tool execution configuration.
Definition tool.hpp:525
bool validate_tool_x_headers(const std::vector< XHeaderEntry > &entries)
Validates x-mcp-header annotations per SEP-2243 constraints.
Definition tool.hpp:1060
std::string encode_header_value(std::string_view value)
Encodes a string value for an Mcp-Param-* header per SEP-2243.
Definition tool.hpp:1126
ToolDefinitionBuilder tool_definition(std::string name)
Creates a fluent builder for advertised MCP tool metadata.
Definition tool.hpp:330
bool is_valid_base64(std::string_view value) noexcept
Returns true when value is canonical RFC 4648 base64 with padding.
Definition tool.hpp:485
bool needs_base64_encoding(std::string_view value)
Checks if a string value needs Base64 encoding per SEP-2243.
Definition tool.hpp:1082
core::Error tool_json_error(std::string message)
Builds an InvalidRequest error for tool JSON validation failures.
Definition tool.hpp:431
TaskSupport
Per-tool support mode for task-based invocation.
Definition tool.hpp:34
@ Forbidden
Clients must not invoke this tool as a task.
@ Optional
Clients may invoke this tool normally or as a task.
Json tool_definition_to_json(const ToolDefinition &definition)
Serializes a tool definition as returned by tool discovery.
Definition tool.hpp:655
core::Result< ToolsListResult > tools_list_result_from_json(const Json &json)
Parses a tools/list result.
Definition tool.hpp:821
Json tools_list_result_to_json(const ToolsListResult &result)
Serializes a tools/list result.
Definition tool.hpp:797
core::Result< std::string > required_content_string(const Json &json, std::string_view member, std::string message)
Reads a required string member from a content object.
Definition tool.hpp:562
std::string number_to_header_string(const Json &value)
Converts a JSON number to its string representation.
Definition tool.hpp:1134
std::vector< XHeaderEntry > extract_x_mcp_headers(const Json &input_schema)
Extracts x-mcp-header annotations from a tool inputSchema.
Definition tool.hpp:1036
Json content_block_to_json(const ContentBlock &block)
Serializes a content block.
Definition tool.hpp:530
core::Result< ToolCall > tool_call_from_json(const Json &json)
Parses tools/call params.
Definition tool.hpp:884
core::Result< ContentBlock > content_block_from_json(const Json &json)
Parses a content block from JSON.
Definition tool.hpp:573
std::optional< TaskSupport > task_support_from_string(std::string_view value) noexcept
Parses a lowercase task support wire value.
Definition tool.hpp:450
std::string base64_encode(std::string_view input)
Standard RFC 4648 base64 encoding.
Definition tool.hpp:1094
Reflection specializations for DTOs defined in types.hpp.
Json icon_to_json(const Icon &icon)
Serializes a shared icon descriptor.
Definition types_reflect.hpp:109
std::optional< Icon > icon_from_json(const Json &json)
Parses a shared icon descriptor.
Definition types_reflect.hpp:114