cxxmcp 1.1.6
C++ MCP SDK
Loading...
Searching...
No Matches
sampling.hpp
Go to the documentation of this file.
1// Copyright (c) 2025 [caomengxuan666]
2
3#pragma once
4
12
13#include <algorithm>
14#include <cmath>
15#include <optional>
16#include <string>
17#include <string_view>
18#include <utility>
19#include <vector>
20
25
26namespace mcp::protocol {
27
29enum class ContextInclusion {
33 None,
36};
37
39enum class Role {
41 User,
44};
45
47inline std::string_view to_string(Role role) noexcept {
48 switch (role) {
49 case Role::User:
50 return "user";
51 case Role::Assistant:
52 return "assistant";
53 }
54 return "user";
55}
56
59inline std::optional<Role> from_string_role(std::string_view value) noexcept {
60 if (value == "user") {
61 return Role::User;
62 }
63 if (value == "assistant") {
64 return Role::Assistant;
65 }
66 return std::nullopt;
67}
68
70enum class ToolChoiceMode {
72 Auto,
76 None,
77};
78
80struct ToolChoice {
82 std::optional<ToolChoiceMode> mode;
84 Json extensions = Json::object();
85
87 static ToolChoice auto_choice() { return ToolChoice{ToolChoiceMode::Auto}; }
88
90 static ToolChoice required() { return ToolChoice{ToolChoiceMode::Required}; }
91
93 static ToolChoice none() { return ToolChoice{ToolChoiceMode::None}; }
94};
95
99 std::string id;
101 std::string name;
103 Json input = Json::object();
105 std::optional<Json> meta;
107 Json extensions = Json::object();
108};
109
113 std::string tool_use_id;
115 std::vector<ContentBlock> content;
117 std::optional<Json> structured_content;
119 std::optional<bool> is_error;
121 std::optional<Json> meta;
123 Json extensions = Json::object();
124};
125
135 std::optional<ToolUseContent> tool_use;
137 std::optional<ToolResultContent> tool_result;
138
140 static SamplingMessageContent text(std::string value) {
142 item.content = ContentBlock::text_content(std::move(value));
143 return item;
144 }
145
149 item.content = std::move(value);
150 return item;
151 }
152
156 item.content.type = "tool_use";
157 item.tool_use = std::move(value);
158 return item;
159 }
160
164 item.content.type = "tool_result";
165 item.tool_result = std::move(value);
166 return item;
167 }
168
170 bool is_tool_use() const noexcept { return tool_use.has_value(); }
171
173 bool is_tool_result() const noexcept { return tool_result.has_value(); }
174};
175
179 std::string role;
183 std::vector<SamplingMessageContent> contents;
185 std::optional<Json> meta;
187 Json extensions = Json::object();
188
190 static SamplingMessage text(std::string role, std::string value) {
191 SamplingMessage message;
192 message.role = std::move(role);
193 message.content = ContentBlock::text_content(std::move(value));
194 return message;
195 }
196};
197
199struct ModelHint {
201 std::string name;
203 Json extensions = Json::object();
204};
205
206template <>
208 static constexpr bool defined = true;
209 static auto fields() {
210 return std::make_tuple(field("name", &ModelHint::name),
212 }
213 static std::vector<std::string> known_keys() { return {"name"}; }
214};
215
219 std::vector<ModelHint> hints;
221 std::optional<double> cost_priority;
223 std::optional<double> speed_priority;
225 std::optional<double> intelligence_priority;
227 Json extensions = Json::object();
228};
229
233 std::vector<SamplingMessage> messages;
235 std::optional<ModelPreferences> model_preferences;
237 std::optional<std::string> system_prompt;
239 std::optional<std::string> include_context;
241 std::optional<double> temperature;
243 int max_tokens = 0;
245 std::vector<std::string> stop_sequences;
247 Json metadata = Json::object();
249 std::optional<TaskRequestParameters> task;
251 std::optional<Json> meta;
253 std::vector<ToolDefinition> tools;
255 std::optional<ToolChoice> tool_choice;
257 Json extensions = Json::object();
258};
259
263 std::string role;
267 std::vector<SamplingMessageContent> contents;
269 std::string model;
271 std::string stop_reason;
273 std::optional<Json> meta;
275 Json extensions = Json::object();
276
278 static CreateMessageResult text(std::string role, std::string value,
279 std::string model = {}) {
280 CreateMessageResult result;
281 result.role = std::move(role);
282 result.content = ContentBlock::text_content(std::move(value));
283 result.model = std::move(model);
284 return result;
285 }
286};
287
289inline core::Error sampling_json_error(std::string message) {
290 return core::Error{
291 static_cast<int>(ErrorCode::InvalidRequest), std::move(message), {}};
292}
293
295inline bool sampling_role_is_valid(std::string_view role) noexcept {
296 return from_string_role(role).has_value();
297}
298
300inline bool sampling_role_is_valid(Role /*role*/) noexcept { return true; }
301
303inline std::string_view context_inclusion_to_string(ContextInclusion mode) {
304 switch (mode) {
305 case ContextInclusion::AllServers:
306 return "allServers";
307 case ContextInclusion::None:
308 return "none";
309 case ContextInclusion::ThisServer:
310 return "thisServer";
311 }
312 return "none";
313}
314
316inline std::optional<ContextInclusion> context_inclusion_from_string(
317 std::string_view value) {
318 if (value == "allServers") {
319 return ContextInclusion::AllServers;
320 }
321 if (value == "none") {
322 return ContextInclusion::None;
323 }
324 if (value == "thisServer") {
325 return ContextInclusion::ThisServer;
326 }
327 return std::nullopt;
328}
329
331inline bool sampling_include_context_is_valid(std::string_view value) noexcept {
332 return context_inclusion_from_string(value).has_value();
333}
334
336inline bool sampling_number_is_finite(double value) noexcept {
337 return std::isfinite(value);
338}
339
342 const SamplingMessage& message) {
343 bool has_tool_result = false;
344 bool has_non_tool_result = false;
345 for (const auto& content : message.contents) {
346 if (content.is_tool_use() && message.role != "assistant") {
347 return mcp::core::unexpected(sampling_json_error(
348 "sampling tool_use content is only allowed in assistant messages"));
349 }
350 if (content.is_tool_result() && message.role != "user") {
351 return mcp::core::unexpected(sampling_json_error(
352 "sampling tool_result content is only allowed in user messages"));
353 }
354 has_tool_result = has_tool_result || content.is_tool_result();
355 has_non_tool_result = has_non_tool_result || !content.is_tool_result();
356 }
357 if (has_tool_result && has_non_tool_result) {
358 return mcp::core::unexpected(sampling_json_error(
359 "sampling tool_result messages must not mix other content types"));
360 }
361 return core::Unit{};
362}
363
366 const std::vector<SamplingMessage>& messages) {
367 std::vector<std::string> pending_tool_use_ids;
368 for (const auto& message : messages) {
369 if (message.role == "assistant") {
370 for (const auto& content : message.contents) {
371 if (content.tool_use.has_value()) {
372 pending_tool_use_ids.push_back(content.tool_use->id);
373 }
374 }
375 continue;
376 }
377 if (message.role != "user") {
378 continue;
379 }
380 for (const auto& content : message.contents) {
381 if (!content.tool_result.has_value()) {
382 continue;
383 }
384 const auto found =
385 std::find(pending_tool_use_ids.begin(), pending_tool_use_ids.end(),
386 content.tool_result->tool_use_id);
387 if (found == pending_tool_use_ids.end()) {
388 return mcp::core::unexpected(sampling_json_error(
389 "sampling tool_result content has no matching tool_use"));
390 }
391 pending_tool_use_ids.erase(found);
392 }
393 }
394 if (!pending_tool_use_ids.empty()) {
395 return mcp::core::unexpected(sampling_json_error(
396 "sampling tool_use content is missing a matching tool_result"));
397 }
398 return core::Unit{};
399}
400
403 switch (mode) {
404 case ToolChoiceMode::Auto:
405 return "auto";
406 case ToolChoiceMode::Required:
407 return "required";
408 case ToolChoiceMode::None:
409 return "none";
410 }
411 return "auto";
412}
413
415inline std::optional<ToolChoiceMode> tool_choice_mode_from_string(
416 const std::string& value) {
417 if (value == "auto") {
418 return ToolChoiceMode::Auto;
419 }
420 if (value == "required") {
421 return ToolChoiceMode::Required;
422 }
423 if (value == "none") {
424 return ToolChoiceMode::None;
425 }
426 return std::nullopt;
427}
428
429template <>
431 static void serialize(Json& json, const char* key, ToolChoiceMode value) {
432 json[key] = tool_choice_mode_to_string(value);
433 }
434 static bool deserialize(const Json& json, const char* key,
435 ToolChoiceMode& target) {
436 if (!json.contains(key) || !json.at(key).is_string()) {
437 return false;
438 }
439 auto val = tool_choice_mode_from_string(json.at(key).get<std::string>());
440 if (!val.has_value()) {
441 return false;
442 }
443 target = *val;
444 return true;
445 }
446};
447
449inline Json tool_choice_to_json(const ToolChoice& choice) {
450 Json json = Json::object();
451 if (choice.mode.has_value()) {
452 json["mode"] = tool_choice_mode_to_string(*choice.mode);
453 }
455 return json;
456}
457
460 if (!json.is_object()) {
461 return mcp::core::unexpected(
462 sampling_json_error("toolChoice must be an object"));
463 }
464 ToolChoice choice;
465 if (json.contains("mode")) {
466 if (!json.at("mode").is_string()) {
467 return mcp::core::unexpected(
468 sampling_json_error("toolChoice mode must be a string"));
469 }
470 const auto mode =
471 tool_choice_mode_from_string(json.at("mode").get<std::string>());
472 if (!mode.has_value()) {
473 return mcp::core::unexpected(
474 sampling_json_error("toolChoice mode is not supported"));
475 }
476 choice.mode = *mode;
477 }
478 choice.extensions = collect_json_extensions(json, {"mode"});
479 return choice;
480}
481
484 Json json = Json::object();
485 json["type"] = "tool_use";
486 json["id"] = content.id;
487 json["name"] = content.name;
488 json["input"] = content.input;
489 if (content.meta.has_value()) {
490 json["_meta"] = *content.meta;
491 }
492 append_json_extensions(json, content.extensions);
493 return json;
494}
495
498 const Json& json) {
499 if (!json.is_object()) {
500 return mcp::core::unexpected(
501 sampling_json_error("tool_use content must be an object"));
502 }
503 if (!json.contains("id") || !json.at("id").is_string()) {
504 return mcp::core::unexpected(
505 sampling_json_error("tool_use content requires a string id"));
506 }
507 if (!json.contains("name") || !json.at("name").is_string()) {
508 return mcp::core::unexpected(
509 sampling_json_error("tool_use content requires a string name"));
510 }
511 if (!json.contains("input") || !json.at("input").is_object()) {
512 return mcp::core::unexpected(
513 sampling_json_error("tool_use content requires object input"));
514 }
515 ToolUseContent content;
516 content.id = json.at("id").get<std::string>();
517 content.name = json.at("name").get<std::string>();
518 content.input = json.at("input");
519 if (json.contains("_meta")) {
520 if (!json.at("_meta").is_object()) {
521 return mcp::core::unexpected(
522 sampling_json_error("tool_use content _meta must be an object"));
523 }
524 content.meta = json.at("_meta");
525 }
526 content.extensions =
527 collect_json_extensions(json, {"type", "id", "name", "input", "_meta"});
528 return content;
529}
530
533 Json json = Json::object();
534 json["type"] = "tool_result";
535 json["toolUseId"] = content.tool_use_id;
536 if (!content.content.empty()) {
537 json["content"] = Json::array();
538 for (const auto& block : content.content) {
539 json["content"].push_back(content_block_to_json(block));
540 }
541 }
542 if (content.structured_content.has_value()) {
543 json["structuredContent"] = *content.structured_content;
544 }
545 if (content.is_error.has_value()) {
546 json["isError"] = *content.is_error;
547 }
548 if (content.meta.has_value()) {
549 json["_meta"] = *content.meta;
550 }
551 append_json_extensions(json, content.extensions);
552 return json;
553}
554
557 const Json& json) {
558 if (!json.is_object()) {
559 return mcp::core::unexpected(
560 sampling_json_error("tool_result content must be an object"));
561 }
562 if (!json.contains("toolUseId") || !json.at("toolUseId").is_string()) {
563 return mcp::core::unexpected(
564 sampling_json_error("tool_result content requires toolUseId"));
565 }
566 ToolResultContent content;
567 content.tool_use_id = json.at("toolUseId").get<std::string>();
568 if (json.contains("content")) {
569 if (!json.at("content").is_array()) {
570 return mcp::core::unexpected(
571 sampling_json_error("tool_result content must be an array"));
572 }
573 for (const auto& item : json.at("content")) {
574 const auto block = content_block_from_json(item);
575 if (!block) {
576 return mcp::core::unexpected(block.error());
577 }
578 content.content.push_back(*block);
579 }
580 }
581 if (json.contains("structuredContent")) {
582 if (!json.at("structuredContent").is_object()) {
583 return mcp::core::unexpected(sampling_json_error(
584 "tool_result structuredContent must be an object"));
585 }
586 content.structured_content = json.at("structuredContent");
587 }
588 if (json.contains("isError")) {
589 if (!json.at("isError").is_boolean()) {
590 return mcp::core::unexpected(
591 sampling_json_error("tool_result isError must be a boolean"));
592 }
593 content.is_error = json.at("isError").get<bool>();
594 }
595 if (json.contains("_meta")) {
596 if (!json.at("_meta").is_object()) {
597 return mcp::core::unexpected(
598 sampling_json_error("tool_result _meta must be an object"));
599 }
600 content.meta = json.at("_meta");
601 }
602 content.extensions =
603 collect_json_extensions(json, {"type", "toolUseId", "content",
604 "structuredContent", "isError", "_meta"});
605 return content;
606}
607
610 const SamplingMessageContent& content) {
611 if (content.tool_use.has_value()) {
612 return tool_use_content_to_json(*content.tool_use);
613 }
614 if (content.tool_result.has_value()) {
616 }
617 return content_block_to_json(content.content);
618}
619
622 const Json& json) {
623 if (json.is_object() && json.contains("type") &&
624 json.at("type").is_string()) {
625 const auto type = json.at("type").get<std::string>();
626 if (type == "tool_use") {
627 const auto tool_use = tool_use_content_from_json(json);
628 if (!tool_use) {
629 return mcp::core::unexpected(tool_use.error());
630 }
632 }
633 if (type == "tool_result") {
634 const auto tool_result = tool_result_content_from_json(json);
635 if (!tool_result) {
636 return mcp::core::unexpected(tool_result.error());
637 }
639 }
640 }
641
642 const auto block = content_block_from_json(json);
643 if (!block) {
644 return mcp::core::unexpected(block.error());
645 }
646 if (block->type == "resource" || block->type == "resource_link") {
647 return mcp::core::unexpected(sampling_json_error(
648 "sampling message content does not support resource content"));
649 }
651}
652
655 Json json = Json{{"role", message.role}};
656 if (!message.contents.empty()) {
657 if (message.contents.size() == 1) {
658 json["content"] =
660 } else {
661 json["content"] = Json::array();
662 for (const auto& content : message.contents) {
663 json["content"].push_back(sampling_message_content_to_json(content));
664 }
665 }
666 } else {
667 json["content"] = content_block_to_json(message.content);
668 }
669 if (message.meta.has_value()) {
670 json["_meta"] = *message.meta;
671 }
672 append_json_extensions(json, message.extensions);
673 return json;
674}
675
679 const Json& json) {
680 if (!json.is_object()) {
681 return mcp::core::unexpected(
682 sampling_json_error("sampling message must be an object"));
683 }
684 if (!json.contains("role") || !json.at("role").is_string()) {
685 return mcp::core::unexpected(
686 sampling_json_error("sampling message requires a string role"));
687 }
688 if (!json.contains("content")) {
689 return mcp::core::unexpected(
690 sampling_json_error("sampling message requires content"));
691 }
692 SamplingMessage message;
693 message.role = json.at("role").get<std::string>();
694 if (!sampling_role_is_valid(message.role)) {
695 return mcp::core::unexpected(
696 sampling_json_error("sampling message role is not supported"));
697 }
698 if (json.at("content").is_array()) {
699 for (const auto& item : json.at("content")) {
700 const auto content = sampling_message_content_from_json(item);
701 if (!content) {
702 return mcp::core::unexpected(content.error());
703 }
704 message.contents.push_back(*content);
705 }
706 } else {
707 const auto content = sampling_message_content_from_json(json.at("content"));
708 if (!content) {
709 return mcp::core::unexpected(content.error());
710 }
711 message.contents.push_back(*content);
712 }
713 if (!message.contents.empty()) {
714 message.content = message.contents.front().content;
715 }
716 if (json.contains("_meta")) {
717 if (!json.at("_meta").is_object()) {
718 return mcp::core::unexpected(
719 sampling_json_error("sampling message _meta must be an object"));
720 }
721 message.meta = json.at("_meta");
722 }
723 message.extensions =
724 collect_json_extensions(json, {"role", "content", "_meta"});
725 if (const auto valid = validate_sampling_message_content_roles(message);
726 !valid) {
727 return mcp::core::unexpected(valid.error());
728 }
729 return message;
730}
731
733inline Json model_hint_to_json(const ModelHint& hint) {
734 return reflect_to_json(hint);
735}
736
740 return reflect_from_json<ModelHint>(json);
741}
742
745 Json json = Json::object();
746 if (!preferences.hints.empty()) {
747 json["hints"] = Json::array();
748 for (const auto& hint : preferences.hints) {
749 json["hints"].push_back(model_hint_to_json(hint));
750 }
751 }
752 if (preferences.cost_priority.has_value()) {
753 json["costPriority"] = *preferences.cost_priority;
754 }
755 if (preferences.speed_priority.has_value()) {
756 json["speedPriority"] = *preferences.speed_priority;
757 }
758 if (preferences.intelligence_priority.has_value()) {
759 json["intelligencePriority"] = *preferences.intelligence_priority;
760 }
761 append_json_extensions(json, preferences.extensions);
762 return json;
763}
764
768 const Json& json) {
769 if (!json.is_object()) {
770 return mcp::core::unexpected(
771 sampling_json_error("model preferences must be an object"));
772 }
773 ModelPreferences preferences;
774 if (json.contains("hints")) {
775 if (!json.at("hints").is_array()) {
776 return mcp::core::unexpected(
777 sampling_json_error("model preferences hints must be an array"));
778 }
779 for (const auto& item : json.at("hints")) {
780 const auto hint = model_hint_from_json(item);
781 if (!hint) {
782 return mcp::core::unexpected(hint.error());
783 }
784 preferences.hints.push_back(*hint);
785 }
786 }
787 const auto read_priority =
788 [&](const char* key,
789 std::optional<double>& target) -> core::Result<core::Unit> {
790 if (!json.contains(key)) {
791 return core::Unit{};
792 }
793 if (!json.at(key).is_number()) {
794 return mcp::core::unexpected(sampling_json_error(
795 std::string("model preferences ") + key + " must be a number"));
796 }
797 const auto value = json.at(key).get<double>();
798 if (!sampling_number_is_finite(value)) {
799 return mcp::core::unexpected(sampling_json_error(
800 std::string("model preferences ") + key + " must be finite"));
801 }
802 target = value;
803 return core::Unit{};
804 };
805 if (const auto ok = read_priority("costPriority", preferences.cost_priority);
806 !ok) {
807 return mcp::core::unexpected(ok.error());
808 }
809 if (const auto ok =
810 read_priority("speedPriority", preferences.speed_priority);
811 !ok) {
812 return mcp::core::unexpected(ok.error());
813 }
814 if (const auto ok = read_priority("intelligencePriority",
815 preferences.intelligence_priority);
816 !ok) {
817 return mcp::core::unexpected(ok.error());
818 }
820 json, {"hints", "costPriority", "speedPriority", "intelligencePriority"});
821 return preferences;
822}
823
826 Json json = Json::object();
827 json["messages"] = Json::array();
828 for (const auto& message : params.messages) {
829 json["messages"].push_back(sampling_message_to_json(message));
830 }
831 if (params.model_preferences.has_value()) {
832 json["modelPreferences"] =
834 }
835 if (params.system_prompt.has_value()) {
836 json["systemPrompt"] = *params.system_prompt;
837 }
838 if (params.include_context.has_value()) {
839 json["includeContext"] = *params.include_context;
840 }
841 if (params.temperature.has_value()) {
842 json["temperature"] = *params.temperature;
843 }
844 json["maxTokens"] = params.max_tokens;
845 if (!params.stop_sequences.empty()) {
846 json["stopSequences"] = params.stop_sequences;
847 }
848 if (!params.metadata.empty()) {
849 json["metadata"] = params.metadata;
850 }
851 if (params.task.has_value()) {
852 json["task"] = task_request_parameters_to_json(*params.task);
853 }
854 if (params.meta.has_value()) {
855 json["_meta"] = *params.meta;
856 }
857 if (!params.tools.empty()) {
858 json["tools"] = Json::array();
859 for (const auto& tool : params.tools) {
860 json["tools"].push_back(tool_definition_to_json(tool));
861 }
862 }
863 if (params.tool_choice.has_value()) {
864 json["toolChoice"] = tool_choice_to_json(*params.tool_choice);
865 }
867 return json;
868}
869
873 const Json& json) {
874 if (!json.is_object()) {
875 return mcp::core::unexpected(
876 sampling_json_error("sampling/createMessage params must be an object"));
877 }
878 if (!json.contains("messages") || !json.at("messages").is_array()) {
879 return mcp::core::unexpected(sampling_json_error(
880 "sampling/createMessage params require a messages array"));
881 }
882 if (!json.contains("maxTokens") ||
883 !json.at("maxTokens").is_number_integer()) {
884 return mcp::core::unexpected(sampling_json_error(
885 "sampling/createMessage params require integer maxTokens"));
886 }
887
888 CreateMessageParams params;
889 for (const auto& item : json.at("messages")) {
890 const auto message = sampling_message_from_json(item);
891 if (!message) {
892 return mcp::core::unexpected(message.error());
893 }
894 params.messages.push_back(*message);
895 }
896 if (json.contains("modelPreferences")) {
897 const auto preferences =
898 model_preferences_from_json(json.at("modelPreferences"));
899 if (!preferences) {
900 return mcp::core::unexpected(preferences.error());
901 }
902 params.model_preferences = *preferences;
903 }
904 if (json.contains("systemPrompt")) {
905 if (!json.at("systemPrompt").is_string()) {
906 return mcp::core::unexpected(
907 sampling_json_error("sampling systemPrompt must be a string"));
908 }
909 params.system_prompt = json.at("systemPrompt").get<std::string>();
910 }
911 if (json.contains("includeContext")) {
912 if (!json.at("includeContext").is_string()) {
913 return mcp::core::unexpected(
914 sampling_json_error("sampling includeContext must be a string"));
915 }
916 params.include_context = json.at("includeContext").get<std::string>();
918 return mcp::core::unexpected(
919 sampling_json_error("sampling includeContext is not supported"));
920 }
921 }
922 if (json.contains("temperature")) {
923 if (!json.at("temperature").is_number()) {
924 return mcp::core::unexpected(
925 sampling_json_error("sampling temperature must be a number"));
926 }
927 const auto temperature = json.at("temperature").get<double>();
928 if (!sampling_number_is_finite(temperature)) {
929 return mcp::core::unexpected(
930 sampling_json_error("sampling temperature must be finite"));
931 }
932 params.temperature = temperature;
933 }
934 params.max_tokens = json.at("maxTokens").get<int>();
935 if (json.contains("stopSequences")) {
936 if (!json.at("stopSequences").is_array()) {
937 return mcp::core::unexpected(
938 sampling_json_error("sampling stopSequences must be an array"));
939 }
940 for (const auto& item : json.at("stopSequences")) {
941 if (!item.is_string()) {
942 return mcp::core::unexpected(
943 sampling_json_error("sampling stopSequences must contain strings"));
944 }
945 params.stop_sequences.push_back(item.get<std::string>());
946 }
947 }
948 if (json.contains("metadata")) {
949 if (!json.at("metadata").is_object()) {
950 return mcp::core::unexpected(
951 sampling_json_error("sampling metadata must be an object"));
952 }
953 params.metadata = json.at("metadata");
954 }
955 if (json.contains("task")) {
956 const auto task = task_request_parameters_from_json(json.at("task"));
957 if (!task) {
958 return mcp::core::unexpected(task.error());
959 }
960 params.task = *task;
961 }
962 if (json.contains("_meta")) {
963 if (!json.at("_meta").is_object()) {
964 return mcp::core::unexpected(sampling_json_error(
965 "sampling/createMessage _meta must be an object"));
966 }
967 params.meta = json.at("_meta");
968 }
969 if (json.contains("tools")) {
970 if (!json.at("tools").is_array()) {
971 return mcp::core::unexpected(
972 sampling_json_error("sampling tools must be an array"));
973 }
974 for (const auto& item : json.at("tools")) {
975 const auto tool = tool_definition_from_json(item);
976 if (!tool) {
977 return mcp::core::unexpected(tool.error());
978 }
979 params.tools.push_back(*tool);
980 }
981 }
982 if (json.contains("toolChoice")) {
983 const auto tool_choice = tool_choice_from_json(json.at("toolChoice"));
984 if (!tool_choice) {
985 return mcp::core::unexpected(tool_choice.error());
986 }
987 params.tool_choice = *tool_choice;
988 }
990 json, {"messages", "modelPreferences", "systemPrompt", "includeContext",
991 "temperature", "maxTokens", "stopSequences", "metadata", "task",
992 "_meta", "tools", "toolChoice"});
993 if (const auto valid =
995 !valid) {
996 return mcp::core::unexpected(valid.error());
997 }
998 return params;
999}
1000
1003 Json json = Json::object();
1004 json["role"] = result.role;
1005 if (!result.contents.empty()) {
1006 if (result.contents.size() == 1) {
1007 json["content"] =
1009 } else {
1010 json["content"] = Json::array();
1011 for (const auto& content : result.contents) {
1012 json["content"].push_back(sampling_message_content_to_json(content));
1013 }
1014 }
1015 } else {
1016 json["content"] = content_block_to_json(result.content);
1017 }
1018 json["model"] = result.model;
1019 if (!result.stop_reason.empty()) {
1020 json["stopReason"] = result.stop_reason;
1021 }
1022 if (result.meta.has_value()) {
1023 json["_meta"] = *result.meta;
1024 }
1025 append_json_extensions(json, result.extensions);
1026 return json;
1027}
1028
1032 const Json& json) {
1033 if (!json.is_object()) {
1034 return mcp::core::unexpected(
1035 sampling_json_error("sampling/createMessage result must be an object"));
1036 }
1037 if (!json.contains("role") || !json.at("role").is_string()) {
1038 return mcp::core::unexpected(sampling_json_error(
1039 "sampling/createMessage result requires a string role"));
1040 }
1041 if (!json.contains("content")) {
1042 return mcp::core::unexpected(
1043 sampling_json_error("sampling/createMessage result requires content"));
1044 }
1045 if (!json.contains("model") || !json.at("model").is_string()) {
1046 return mcp::core::unexpected(sampling_json_error(
1047 "sampling/createMessage result requires a string model"));
1048 }
1049 CreateMessageResult result;
1050 result.role = json.at("role").get<std::string>();
1051 if (result.role != "assistant") {
1052 return mcp::core::unexpected(sampling_json_error(
1053 "sampling/createMessage result role must be assistant"));
1054 }
1055 if (json.at("content").is_array()) {
1056 for (const auto& item : json.at("content")) {
1057 const auto content = sampling_message_content_from_json(item);
1058 if (!content) {
1059 return mcp::core::unexpected(content.error());
1060 }
1061 result.contents.push_back(*content);
1062 }
1063 } else {
1064 const auto content = sampling_message_content_from_json(json.at("content"));
1065 if (!content) {
1066 return mcp::core::unexpected(content.error());
1067 }
1068 result.contents.push_back(*content);
1069 }
1070 if (!result.contents.empty()) {
1071 result.content = result.contents.front().content;
1072 }
1073 result.model = json.at("model").get<std::string>();
1074 if (json.contains("stopReason")) {
1075 if (!json.at("stopReason").is_string()) {
1076 return mcp::core::unexpected(
1077 sampling_json_error("sampling stopReason must be a string"));
1078 }
1079 result.stop_reason = json.at("stopReason").get<std::string>();
1080 }
1081 if (json.contains("_meta")) {
1082 if (!json.at("_meta").is_object()) {
1083 return mcp::core::unexpected(sampling_json_error(
1084 "sampling/createMessage result _meta must be an object"));
1085 }
1086 result.meta = json.at("_meta");
1087 }
1089 json, {"role", "content", "model", "stopReason", "_meta"});
1090 SamplingMessage result_message;
1091 result_message.role = result.role;
1092 result_message.contents = result.contents;
1093 if (const auto valid =
1095 !valid) {
1096 return mcp::core::unexpected(valid.error());
1097 }
1098 return result;
1099}
1100
1101} // namespace mcp::protocol
@ User
Message from the user.
@ Assistant
Message from the assistant (model).
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
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
Shared result and error primitives used by the public cxxmcp SDK.
std::monostate Unit
Success value for operations that only need to report failure.
Definition result.hpp:55
tl::expected< T, Error > Result
Alias for the SDK result type.
Definition result.hpp:64
core::Result< ToolUseContent > tool_use_content_from_json(const Json &json)
Parses assistant-side sampling tool use content.
Definition sampling.hpp:497
Role
Message role for sampling messages.
Definition sampling.hpp:39
Json create_message_result_to_json(const CreateMessageResult &result)
Serializes a sampling/createMessage result.
Definition sampling.hpp:1002
ToolChoiceMode
Tool selection mode for SEP-1577 sampling requests.
Definition sampling.hpp:70
@ Auto
Let the model decide whether to use tools.
@ Required
Require at least one tool use.
Json model_hint_to_json(const ModelHint &hint)
Serializes a model hint.
Definition sampling.hpp:733
core::Result< SamplingMessage > sampling_message_from_json(const Json &json)
Parses a sampling message.
Definition sampling.hpp:678
bool sampling_role_is_valid(std::string_view role) noexcept
Returns true for the MCP sampling message roles.
Definition sampling.hpp:295
core::Error sampling_json_error(std::string message)
Builds an InvalidRequest error for sampling JSON validation failures.
Definition sampling.hpp:289
bool sampling_number_is_finite(double value) noexcept
Returns true for finite JSON numbers accepted by sampling.
Definition sampling.hpp:336
std::string tool_choice_mode_to_string(ToolChoiceMode mode)
Converts a tool-choice mode to the lowercase wire value.
Definition sampling.hpp:402
ContextInclusion
Context inclusion mode for sampling requests.
Definition sampling.hpp:29
@ AllServers
Include context from all servers.
@ None
Do not include any server context.
@ ThisServer
Include context only from the requesting server.
core::Result< core::Unit > validate_sampling_message_content_roles(const SamplingMessage &message)
Validates SEP-1577 tool-use/tool-result placement for one message.
Definition sampling.hpp:341
Json sampling_message_content_to_json(const SamplingMessageContent &content)
Serializes one sampling message content item.
Definition sampling.hpp:609
std::optional< ToolChoiceMode > tool_choice_mode_from_string(const std::string &value)
Parses a tool-choice mode string.
Definition sampling.hpp:415
Json sampling_message_to_json(const SamplingMessage &message)
Serializes a sampling message.
Definition sampling.hpp:654
core::Result< ModelHint > model_hint_from_json(const Json &json)
Parses a model hint.
Definition sampling.hpp:739
std::optional< ContextInclusion > context_inclusion_from_string(std::string_view value)
Parses a context-inclusion mode string.
Definition sampling.hpp:316
std::optional< Role > from_string_role(std::string_view value) noexcept
Parses a Role from its wire string.
Definition sampling.hpp:59
Json create_message_params_to_json(const CreateMessageParams &params)
Serializes sampling/createMessage params.
Definition sampling.hpp:825
Json model_preferences_to_json(const ModelPreferences &preferences)
Serializes model preferences.
Definition sampling.hpp:744
core::Result< ToolResultContent > tool_result_content_from_json(const Json &json)
Parses user-side sampling tool result content.
Definition sampling.hpp:556
core::Result< core::Unit > validate_sampling_tool_use_result_balance(const std::vector< SamplingMessage > &messages)
Validates that every sampling tool result answers a prior tool use.
Definition sampling.hpp:365
core::Result< ModelPreferences > model_preferences_from_json(const Json &json)
Parses model preferences.
Definition sampling.hpp:767
Json tool_use_content_to_json(const ToolUseContent &content)
Serializes assistant-side sampling tool use content.
Definition sampling.hpp:483
std::string_view context_inclusion_to_string(ContextInclusion mode)
Converts a context-inclusion mode to the camelCase wire value.
Definition sampling.hpp:303
bool sampling_include_context_is_valid(std::string_view value) noexcept
Returns true for the MCP sampling includeContext values.
Definition sampling.hpp:331
core::Result< ToolChoice > tool_choice_from_json(const Json &json)
Parses tool-choice behavior.
Definition sampling.hpp:459
Json tool_result_content_to_json(const ToolResultContent &content)
Serializes user-side sampling tool result content.
Definition sampling.hpp:532
std::string_view to_string(Role role) noexcept
Converts a Role to its lowercase wire representation.
Definition sampling.hpp:47
core::Result< SamplingMessageContent > sampling_message_content_from_json(const Json &json)
Parses one sampling message content item.
Definition sampling.hpp:621
core::Result< CreateMessageResult > create_message_result_from_json(const Json &json)
Parses a sampling/createMessage result.
Definition sampling.hpp:1031
Json tool_choice_to_json(const ToolChoice &choice)
Serializes tool-choice behavior.
Definition sampling.hpp:449
core::Result< CreateMessageParams > create_message_params_from_json(const Json &json)
Parses sampling/createMessage params.
Definition sampling.hpp:872
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
std::string type
Content kind.
Definition tool.hpp:96
static ContentBlock text_content(std::string value)
Creates a text content block.
Definition tool.hpp:115
Parameters for sampling/createMessage.
Definition sampling.hpp:231
std::optional< std::string > include_context
Optional instruction for whether client context should be included.
Definition sampling.hpp:239
std::optional< std::string > system_prompt
Optional system prompt supplied outside the message array.
Definition sampling.hpp:237
std::optional< double > temperature
Optional sampling temperature. Explicit 0.0 is serialized.
Definition sampling.hpp:241
std::optional< TaskRequestParameters > task
Optional task request parameters for asynchronous sampling.
Definition sampling.hpp:249
Json metadata
Optional metadata object carried through the sampling request.
Definition sampling.hpp:247
Json extensions
Unknown JSON members preserved for forward-compatible round trips.
Definition sampling.hpp:257
std::vector< std::string > stop_sequences
Optional stop sequences.
Definition sampling.hpp:245
std::vector< SamplingMessage > messages
Conversation messages to sample from.
Definition sampling.hpp:233
int max_tokens
Maximum tokens the client may generate.
Definition sampling.hpp:243
std::vector< ToolDefinition > tools
Optional tools available to the sampled model.
Definition sampling.hpp:253
std::optional< ToolChoice > tool_choice
Optional tool selection behavior.
Definition sampling.hpp:255
std::optional< ModelPreferences > model_preferences
Optional model selection preferences.
Definition sampling.hpp:235
std::optional< Json > meta
Optional _meta extension object preserved on the wire.
Definition sampling.hpp:251
Result object for sampling/createMessage.
Definition sampling.hpp:261
std::string stop_reason
Optional reason generation stopped.
Definition sampling.hpp:271
ContentBlock content
First or only generated content kept for simple callers.
Definition sampling.hpp:265
static CreateMessageResult text(std::string role, std::string value, std::string model={})
Creates a text-only sampling result for the given role and model.
Definition sampling.hpp:278
std::vector< SamplingMessageContent > contents
Optional full generated content list. Empty means content is serialized.
Definition sampling.hpp:267
std::optional< Json > meta
Optional _meta extension object preserved on the wire.
Definition sampling.hpp:273
Json extensions
Unknown JSON members preserved for forward-compatible round trips.
Definition sampling.hpp:275
std::string role
Role assigned to the generated message.
Definition sampling.hpp:263
std::string model
Model identifier selected by the client.
Definition sampling.hpp:269
Type-specific serialization and deserialization logic.
Definition reflect.hpp:246
Soft model name hint for sampling.
Definition sampling.hpp:199
Json extensions
Unknown JSON members preserved for forward-compatible round trips.
Definition sampling.hpp:203
std::string name
Model family, name, or alias preferred by the requester.
Definition sampling.hpp:201
Preferences used by the client when choosing a model.
Definition sampling.hpp:217
std::optional< double > speed_priority
Optional priority for low latency, typically normalized by the peer.
Definition sampling.hpp:223
Json extensions
Unknown JSON members preserved for forward-compatible round trips.
Definition sampling.hpp:227
std::optional< double > cost_priority
Optional priority for low cost, typically normalized by the peer.
Definition sampling.hpp:221
std::optional< double > intelligence_priority
Optional priority for model capability, typically normalized by the peer.
Definition sampling.hpp:225
std::vector< ModelHint > hints
Ordered or unordered model hints supplied by the requester.
Definition sampling.hpp:219
Primary template.
Definition reflect.hpp:199
One sampling message content item.
Definition sampling.hpp:131
std::optional< ToolUseContent > tool_use
Assistant-only tool-use content.
Definition sampling.hpp:135
static SamplingMessageContent tool_use_content(ToolUseContent value)
Creates an assistant tool-use content item.
Definition sampling.hpp:154
std::optional< ToolResultContent > tool_result
User-only tool-result content.
Definition sampling.hpp:137
static SamplingMessageContent text(std::string value)
Creates a text sampling content item.
Definition sampling.hpp:140
static SamplingMessageContent from_content(ContentBlock value)
Wraps a regular content block.
Definition sampling.hpp:147
bool is_tool_use() const noexcept
Returns true when this item carries tool-use content.
Definition sampling.hpp:170
ContentBlock content
Ordinary content block for text, image, audio, or extension content.
Definition sampling.hpp:133
bool is_tool_result() const noexcept
Returns true when this item carries tool-result content.
Definition sampling.hpp:173
static SamplingMessageContent tool_result_content(ToolResultContent value)
Creates a user tool-result content item.
Definition sampling.hpp:162
Input message supplied to a sampling request.
Definition sampling.hpp:177
ContentBlock content
First or only message content block kept for simple callers.
Definition sampling.hpp:181
static SamplingMessage text(std::string role, std::string value)
Creates a text-only sampling message for the given role.
Definition sampling.hpp:190
std::string role
Message role understood by the sampling client.
Definition sampling.hpp:179
std::vector< SamplingMessageContent > contents
Optional full content list. Empty means content is serialized.
Definition sampling.hpp:183
std::optional< Json > meta
Optional _meta extension object preserved on the wire.
Definition sampling.hpp:185
Json extensions
Unknown JSON members preserved for forward-compatible round trips.
Definition sampling.hpp:187
Tool selection behavior supplied to sampling/createMessage.
Definition sampling.hpp:80
static ToolChoice required()
Requires the model to use a tool.
Definition sampling.hpp:90
static ToolChoice none()
Prevents model tool use.
Definition sampling.hpp:93
std::optional< ToolChoiceMode > mode
Optional selection mode. Missing preserves an empty object if present.
Definition sampling.hpp:82
Json extensions
Unknown JSON members preserved for forward-compatible round trips.
Definition sampling.hpp:84
static ToolChoice auto_choice()
Lets the model decide whether to use tools.
Definition sampling.hpp:87
User-side result for a prior sampling tool call.
Definition sampling.hpp:111
std::string tool_use_id
Id of the corresponding tool use.
Definition sampling.hpp:113
std::optional< Json > structured_content
Optional structured result object.
Definition sampling.hpp:117
std::vector< ContentBlock > content
Tool-visible content blocks returned by the tool.
Definition sampling.hpp:115
std::optional< Json > meta
Optional _meta extension object preserved on the wire.
Definition sampling.hpp:121
Json extensions
Unknown JSON members preserved for forward-compatible round trips.
Definition sampling.hpp:123
std::optional< bool > is_error
Optional error marker for failed tool execution.
Definition sampling.hpp:119
Assistant-side tool call request embedded in sampling content.
Definition sampling.hpp:97
Json extensions
Unknown JSON members preserved for forward-compatible round trips.
Definition sampling.hpp:107
std::string name
Tool name to invoke.
Definition sampling.hpp:101
std::string id
Unique id for this requested tool call.
Definition sampling.hpp:99
std::optional< Json > meta
Optional _meta extension object preserved on the wire.
Definition sampling.hpp:105
Json input
JSON object containing tool arguments.
Definition sampling.hpp:103
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
Tool definition, call, and result payloads.
core::Result< ToolDefinition > tool_definition_from_json(const Json &json)
Parses a tool definition from JSON.
Definition tool.hpp:695
Json tool_definition_to_json(const ToolDefinition &definition)
Serializes a tool definition as returned by tool discovery.
Definition tool.hpp:655
Json content_block_to_json(const ContentBlock &block)
Serializes a content block.
Definition tool.hpp:530
core::Result< ContentBlock > content_block_from_json(const Json &json)
Parses a content block from JSON.
Definition tool.hpp:573