1use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct McpSchema {
9 pub name: String,
11 #[serde(default)]
13 pub description: String,
14 #[serde(rename = "inputSchema", default)]
16 pub input_schema: McpInputSchema,
17}
18
19#[derive(Debug, Clone, Default, Serialize, Deserialize)]
21pub struct McpInputSchema {
22 #[serde(rename = "type", default)]
24 pub schema_type: String,
25 #[serde(default)]
27 pub properties: HashMap<String, McpProperty>,
28 #[serde(default)]
30 pub required: Vec<String>,
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct McpProperty {
36 #[serde(rename = "type")]
38 pub prop_type: String,
39 #[serde(default)]
41 pub description: String,
42 #[serde(rename = "enum", default)]
44 pub enum_values: Vec<String>,
45 #[serde(default)]
47 pub default: Option<serde_json::Value>,
48}
49
50#[derive(Debug, Clone)]
52pub struct DcpSchema {
53 pub name: String,
55 pub tool_id: u16,
57 pub description: String,
59 pub fields: Vec<DcpField>,
61 pub required_mask: u64,
63}
64
65#[derive(Debug, Clone)]
67pub struct DcpField {
68 pub name: String,
70 pub field_type: DcpFieldType,
72 pub offset: u16,
74 pub size: u16,
76}
77
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
80#[repr(u8)]
81pub enum DcpFieldType {
82 Null = 0,
83 Bool = 1,
84 I32 = 2,
85 I64 = 3,
86 F64 = 4,
87 String = 5,
88 Bytes = 6,
89 Array = 7,
90 Object = 8,
91}
92
93impl DcpFieldType {
94 pub fn default_size(&self) -> u16 {
96 match self {
97 Self::Null => 0,
98 Self::Bool => 1,
99 Self::I32 => 4,
100 Self::I64 => 8,
101 Self::F64 => 8,
102 Self::String => 8, Self::Bytes => 8, Self::Array => 8, Self::Object => 8, }
107 }
108
109 pub fn from_mcp_type(mcp_type: &str) -> Self {
111 match mcp_type {
112 "boolean" => Self::Bool,
113 "integer" => Self::I64,
114 "number" => Self::F64,
115 "string" => Self::String,
116 "array" => Self::Array,
117 "object" => Self::Object,
118 "null" => Self::Null,
119 _ => Self::String, }
121 }
122}
123
124impl DcpSchema {
125 pub fn to_bytes(&self) -> Vec<u8> {
127 let mut bytes = Vec::new();
128
129 bytes.extend_from_slice(&[0xDC, 0x53]); bytes.push(1); bytes.extend_from_slice(&self.tool_id.to_le_bytes());
133
134 let name_bytes = self.name.as_bytes();
135 bytes.push(name_bytes.len() as u8);
136
137 let desc_bytes = self.description.as_bytes();
138 bytes.extend_from_slice(&(desc_bytes.len() as u16).to_le_bytes());
139
140 bytes.push(self.fields.len() as u8);
141 bytes.extend_from_slice(&self.required_mask.to_le_bytes());
142
143 bytes.extend_from_slice(name_bytes);
145
146 bytes.extend_from_slice(desc_bytes);
148
149 for field in &self.fields {
151 let field_name = field.name.as_bytes();
152 bytes.push(field_name.len() as u8);
153 bytes.extend_from_slice(field_name);
154 bytes.push(field.field_type as u8);
155 bytes.extend_from_slice(&field.offset.to_le_bytes());
156 bytes.extend_from_slice(&field.size.to_le_bytes());
157 }
158
159 bytes
160 }
161
162 pub fn from_bytes(bytes: &[u8]) -> Result<Self, String> {
164 if bytes.len() < 17 {
165 return Err("Insufficient data for schema header".to_string());
166 }
167
168 if bytes[0] != 0xDC || bytes[1] != 0x53 {
170 return Err("Invalid schema magic number".to_string());
171 }
172
173 if bytes[2] != 1 {
175 return Err(format!("Unsupported schema version: {}", bytes[2]));
176 }
177
178 let tool_id = u16::from_le_bytes([bytes[3], bytes[4]]);
179 let name_len = bytes[5] as usize;
180 let desc_len = u16::from_le_bytes([bytes[6], bytes[7]]) as usize;
181 let field_count = bytes[8] as usize;
182 let required_mask = u64::from_le_bytes([
183 bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15], bytes[16],
184 ]);
185
186 let mut offset = 17;
187
188 if offset + name_len > bytes.len() {
190 return Err("Insufficient data for name".to_string());
191 }
192 let name = String::from_utf8(bytes[offset..offset + name_len].to_vec())
193 .map_err(|e| format!("Invalid name encoding: {}", e))?;
194 offset += name_len;
195
196 if offset + desc_len > bytes.len() {
198 return Err("Insufficient data for description".to_string());
199 }
200 let description = String::from_utf8(bytes[offset..offset + desc_len].to_vec())
201 .map_err(|e| format!("Invalid description encoding: {}", e))?;
202 offset += desc_len;
203
204 let mut fields = Vec::with_capacity(field_count);
206 for _ in 0..field_count {
207 if offset >= bytes.len() {
208 return Err("Insufficient data for field".to_string());
209 }
210
211 let field_name_len = bytes[offset] as usize;
212 offset += 1;
213
214 if offset + field_name_len + 5 > bytes.len() {
215 return Err("Insufficient data for field data".to_string());
216 }
217
218 let field_name = String::from_utf8(bytes[offset..offset + field_name_len].to_vec())
219 .map_err(|e| format!("Invalid field name encoding: {}", e))?;
220 offset += field_name_len;
221
222 let field_type = match bytes[offset] {
223 0 => DcpFieldType::Null,
224 1 => DcpFieldType::Bool,
225 2 => DcpFieldType::I32,
226 3 => DcpFieldType::I64,
227 4 => DcpFieldType::F64,
228 5 => DcpFieldType::String,
229 6 => DcpFieldType::Bytes,
230 7 => DcpFieldType::Array,
231 8 => DcpFieldType::Object,
232 _ => return Err(format!("Invalid field type: {}", bytes[offset])),
233 };
234 offset += 1;
235
236 let field_offset = u16::from_le_bytes([bytes[offset], bytes[offset + 1]]);
237 offset += 2;
238
239 let field_size = u16::from_le_bytes([bytes[offset], bytes[offset + 1]]);
240 offset += 2;
241
242 fields.push(DcpField {
243 name: field_name,
244 field_type,
245 offset: field_offset,
246 size: field_size,
247 });
248 }
249
250 Ok(Self {
251 name,
252 tool_id,
253 description,
254 fields,
255 required_mask,
256 })
257 }
258}
259
260pub fn convert_mcp_to_dcp(mcp: &McpSchema) -> Result<DcpSchema, String> {
262 let tool_id = {
264 let hash = blake3::hash(mcp.name.as_bytes());
265 let bytes = hash.as_bytes();
266 u16::from_le_bytes([bytes[0], bytes[1]])
267 };
268
269 let mut fields = Vec::new();
271 let mut current_offset: u16 = 0;
272 let mut required_mask: u64 = 0;
273
274 let mut prop_names: Vec<_> = mcp.input_schema.properties.keys().collect();
276 prop_names.sort();
277
278 for (idx, name) in prop_names.iter().enumerate() {
279 if idx >= 64 {
280 return Err("Too many fields (max 64)".to_string());
281 }
282
283 let prop = &mcp.input_schema.properties[*name];
284 let field_type = DcpFieldType::from_mcp_type(&prop.prop_type);
285 let size = field_type.default_size();
286
287 fields.push(DcpField {
288 name: (*name).clone(),
289 field_type,
290 offset: current_offset,
291 size,
292 });
293
294 if mcp.input_schema.required.contains(name) {
296 required_mask |= 1 << idx;
297 }
298
299 current_offset += size;
300 }
301
302 Ok(DcpSchema {
303 name: mcp.name.clone(),
304 tool_id,
305 description: mcp.description.clone(),
306 fields,
307 required_mask,
308 })
309}
310
311pub fn convert_dcp_to_mcp(dcp: &DcpSchema) -> McpSchema {
313 let mut properties = HashMap::new();
314
315 for field in dcp.fields.iter() {
316 let prop_type = match field.field_type {
317 DcpFieldType::Null => "null",
318 DcpFieldType::Bool => "boolean",
319 DcpFieldType::I32 | DcpFieldType::I64 => "integer",
320 DcpFieldType::F64 => "number",
321 DcpFieldType::String => "string",
322 DcpFieldType::Bytes => "string", DcpFieldType::Array => "array",
324 DcpFieldType::Object => "object",
325 };
326
327 properties.insert(
328 field.name.clone(),
329 McpProperty {
330 prop_type: prop_type.to_string(),
331 description: String::new(),
332 enum_values: Vec::new(),
333 default: None,
334 },
335 );
336 }
337
338 let required: Vec<String> = dcp
340 .fields
341 .iter()
342 .enumerate()
343 .filter(|(idx, _)| (dcp.required_mask & (1 << idx)) != 0)
344 .map(|(_, field)| field.name.clone())
345 .collect();
346
347 McpSchema {
348 name: dcp.name.clone(),
349 description: dcp.description.clone(),
350 input_schema: McpInputSchema {
351 schema_type: "object".to_string(),
352 properties,
353 required,
354 },
355 }
356}
357
358#[cfg(test)]
359mod tests {
360 use super::*;
361
362 #[test]
363 fn test_convert_simple_schema() {
364 let mcp = McpSchema {
365 name: "test_tool".to_string(),
366 description: "A test tool".to_string(),
367 input_schema: McpInputSchema {
368 schema_type: "object".to_string(),
369 properties: {
370 let mut props = HashMap::new();
371 props.insert(
372 "name".to_string(),
373 McpProperty {
374 prop_type: "string".to_string(),
375 description: "The name".to_string(),
376 enum_values: Vec::new(),
377 default: None,
378 },
379 );
380 props.insert(
381 "count".to_string(),
382 McpProperty {
383 prop_type: "integer".to_string(),
384 description: "The count".to_string(),
385 enum_values: Vec::new(),
386 default: None,
387 },
388 );
389 props
390 },
391 required: vec!["name".to_string()],
392 },
393 };
394
395 let dcp = convert_mcp_to_dcp(&mcp).unwrap();
396
397 assert_eq!(dcp.name, "test_tool");
398 assert_eq!(dcp.description, "A test tool");
399 assert_eq!(dcp.fields.len(), 2);
400 }
401
402 #[test]
403 fn test_schema_round_trip() {
404 let mcp = McpSchema {
405 name: "round_trip".to_string(),
406 description: "Test round trip".to_string(),
407 input_schema: McpInputSchema {
408 schema_type: "object".to_string(),
409 properties: {
410 let mut props = HashMap::new();
411 props.insert(
412 "field1".to_string(),
413 McpProperty {
414 prop_type: "string".to_string(),
415 description: String::new(),
416 enum_values: Vec::new(),
417 default: None,
418 },
419 );
420 props
421 },
422 required: vec!["field1".to_string()],
423 },
424 };
425
426 let dcp = convert_mcp_to_dcp(&mcp).unwrap();
427 let bytes = dcp.to_bytes();
428 let restored = DcpSchema::from_bytes(&bytes).unwrap();
429
430 assert_eq!(restored.name, dcp.name);
431 assert_eq!(restored.tool_id, dcp.tool_id);
432 assert_eq!(restored.fields.len(), dcp.fields.len());
433 assert_eq!(restored.required_mask, dcp.required_mask);
434 }
435
436 #[test]
437 fn test_field_type_conversion() {
438 assert_eq!(DcpFieldType::from_mcp_type("boolean"), DcpFieldType::Bool);
439 assert_eq!(DcpFieldType::from_mcp_type("integer"), DcpFieldType::I64);
440 assert_eq!(DcpFieldType::from_mcp_type("number"), DcpFieldType::F64);
441 assert_eq!(DcpFieldType::from_mcp_type("string"), DcpFieldType::String);
442 assert_eq!(DcpFieldType::from_mcp_type("array"), DcpFieldType::Array);
443 assert_eq!(DcpFieldType::from_mcp_type("object"), DcpFieldType::Object);
444 }
445
446 #[test]
447 fn test_dcp_to_mcp_conversion() {
448 let dcp = DcpSchema {
449 name: "test".to_string(),
450 tool_id: 1,
451 description: "Test".to_string(),
452 fields: vec![DcpField {
453 name: "field1".to_string(),
454 field_type: DcpFieldType::String,
455 offset: 0,
456 size: 8,
457 }],
458 required_mask: 1,
459 };
460
461 let mcp = convert_dcp_to_mcp(&dcp);
462
463 assert_eq!(mcp.name, "test");
464 assert!(mcp.input_schema.properties.contains_key("field1"));
465 assert!(mcp.input_schema.required.contains(&"field1".to_string()));
466 }
467}