turbomcp_types/wire.rs
1//! MCP protocol wire-wrapper types.
2//!
3//! Request/response/notification envelopes exchanged over the wire by MCP
4//! clients and servers. These are the canonical definitions; `turbomcp-protocol`
5//! re-exports them verbatim.
6//!
7//! Kept `no_std + alloc` compatible so WASM and embedded consumers can depend
8//! on the same types as native transports.
9
10#[cfg(not(feature = "std"))]
11use alloc::{collections::BTreeMap as HashMap, string::String, vec, vec::Vec};
12#[cfg(feature = "std")]
13use std::collections::HashMap;
14
15use serde::{Deserialize, Serialize};
16use serde_json::Value;
17
18use crate::content::{Content, PromptMessage};
19use crate::definitions::Implementation;
20use crate::protocol::{ClientCapabilities, ServerCapabilities};
21
22// =============================================================================
23// Initialization handshake
24// =============================================================================
25
26/// The `initialize` request sent by the client as the first message after connection.
27///
28/// Used to exchange capabilities and agree on a protocol version for the session.
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct InitializeRequest {
31 /// The protocol version the client wishes to use.
32 #[serde(rename = "protocolVersion")]
33 pub protocol_version: crate::primitives::ProtocolVersion,
34 /// The capabilities supported by the client.
35 pub capabilities: ClientCapabilities,
36 /// Information about the client's implementation (e.g., name, version).
37 #[serde(rename = "clientInfo")]
38 pub client_info: Implementation,
39 /// Optional metadata for the request.
40 ///
41 /// Per MCP 2025-11-25, `_meta` is always `{ [key: string]: unknown }` —
42 /// modelled here as `HashMap<String, Value>` so non-object values are
43 /// rejected at deserialize time.
44 #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
45 pub meta: Option<HashMap<String, Value>>,
46}
47
48/// The response to a successful `initialize` request.
49///
50/// Server confirms connection parameters and declares its own capabilities.
51#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct InitializeResult {
53 /// The protocol version that will be used for the session, chosen by the server.
54 #[serde(rename = "protocolVersion")]
55 pub protocol_version: crate::primitives::ProtocolVersion,
56 /// The capabilities supported by the server.
57 pub capabilities: ServerCapabilities,
58 /// Information about the server's implementation (e.g., name, version).
59 #[serde(rename = "serverInfo")]
60 pub server_info: Implementation,
61 /// Optional human-readable instructions for the client.
62 #[serde(skip_serializing_if = "Option::is_none")]
63 pub instructions: Option<String>,
64 /// Optional metadata for the result. See note on `InitializeRequest::meta`.
65 #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
66 pub meta: Option<HashMap<String, Value>>,
67}
68
69/// Sent by the client after a successful `InitializeResult` to confirm readiness.
70///
71/// This notification has no parameters.
72#[derive(Debug, Clone, Default, Serialize, Deserialize)]
73pub struct InitializedNotification {}
74
75// =============================================================================
76// Tool invocation
77// =============================================================================
78
79/// The result of a `CallToolRequest`.
80#[derive(Debug, Clone, Serialize, Deserialize, Default)]
81pub struct CallToolResult {
82 /// The output of the tool as a series of content blocks. Required.
83 pub content: Vec<Content>,
84 /// Whether the tool execution resulted in an error.
85 ///
86 /// When `true`, all content blocks should be treated as error information;
87 /// the message may span multiple text blocks for structured error reporting.
88 #[serde(rename = "isError", skip_serializing_if = "Option::is_none")]
89 pub is_error: Option<bool>,
90 /// Optional structured output conforming to the tool's `output_schema`.
91 ///
92 /// Tools that emit structured content SHOULD also include the serialized
93 /// JSON in a `TextContent` block for clients that don't support structured
94 /// output.
95 #[serde(rename = "structuredContent", skip_serializing_if = "Option::is_none")]
96 pub structured_content: Option<Value>,
97 /// Optional metadata for the result.
98 ///
99 /// For client applications and tools to pass context that should NOT be
100 /// exposed to LLMs (tracking IDs, metrics, cache status, etc.).
101 #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
102 pub meta: Option<HashMap<String, Value>>,
103}
104
105impl CallToolResult {
106 /// Create a successful result with a single text content block.
107 #[must_use]
108 pub fn text(text: impl Into<String>) -> Self {
109 Self {
110 content: vec![Content::text(text)],
111 ..Default::default()
112 }
113 }
114
115 /// Create an error result with a single text content block and `is_error = true`.
116 #[must_use]
117 pub fn error(message: impl Into<String>) -> Self {
118 Self {
119 content: vec![Content::text(message)],
120 is_error: Some(true),
121 ..Default::default()
122 }
123 }
124
125 /// Create a successful JSON result (pretty-printed text content).
126 pub fn json<T: Serialize>(value: &T) -> Result<Self, serde_json::Error> {
127 let text = serde_json::to_string_pretty(value)?;
128 Ok(Self::text(text))
129 }
130
131 /// Create a result with multiple content items.
132 #[must_use]
133 pub fn contents(contents: Vec<Content>) -> Self {
134 Self {
135 content: contents,
136 ..Default::default()
137 }
138 }
139
140 /// Create an image result (base64-encoded).
141 #[must_use]
142 pub fn image(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
143 Self {
144 content: vec![Content::image(data, mime_type)],
145 ..Default::default()
146 }
147 }
148
149 /// Extracts and concatenates all text content (newline-joined).
150 ///
151 /// Returns an empty string if no text blocks are present.
152 pub fn all_text(&self) -> String {
153 let texts: Vec<&str> = self.content.iter().filter_map(Content::as_text).collect();
154 texts.join("\n")
155 }
156
157 /// Returns the text of the first text block, if any.
158 pub fn first_text(&self) -> Option<&str> {
159 self.content.first().and_then(Content::as_text)
160 }
161
162 /// Whether `is_error` is explicitly `true`.
163 pub fn has_error(&self) -> bool {
164 self.is_error.unwrap_or(false)
165 }
166}
167
168// =============================================================================
169// Prompt retrieval
170// =============================================================================
171
172/// The result of a `prompts/get` request.
173#[derive(Debug, Clone, Default, Serialize, Deserialize)]
174pub struct GetPromptResult {
175 /// Optional description of this prompt.
176 #[serde(skip_serializing_if = "Option::is_none")]
177 pub description: Option<String>,
178 /// The sequence of messages that compose the prompt.
179 pub messages: Vec<PromptMessage>,
180 /// Optional metadata for the result.
181 #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
182 pub meta: Option<HashMap<String, Value>>,
183}