use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolContext {
pub server_id: String,
pub name: String,
pub name_literal: String,
pub server_id_literal: String,
pub typescript_name: String,
pub description: String,
pub input_schema: serde_json::Value,
pub properties: Vec<PropertyInfo>,
pub category: Option<String>,
pub keywords: Option<String>,
pub short_description: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PropertyInfo {
pub name: String,
pub typescript_type: String,
pub description: Option<String>,
pub required: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IndexContext {
pub server_name: String,
pub server_version: String,
pub tool_count: usize,
pub tools: Vec<ToolSummary>,
#[serde(skip_serializing_if = "Option::is_none")]
pub categories: Option<Vec<CategoryInfo>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolSummary {
pub typescript_name: String,
pub description: String,
pub category: Option<String>,
pub keywords: Option<String>,
pub short_description: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolCategorization {
pub category: String,
pub keywords: Vec<String>,
pub short_description: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CategoryInfo {
pub name: String,
pub tools: Vec<ToolSummary>,
}
#[allow(clippy::struct_field_names)]
#[derive(Debug, Clone, Serialize)]
pub struct BridgeContext {
forbidden_chars: Vec<String>,
forbidden_env_names: Vec<String>,
forbidden_env_prefix: String,
}
impl BridgeContext {
#[must_use]
pub fn forbidden_chars(&self) -> &[String] {
&self.forbidden_chars
}
#[must_use]
pub fn forbidden_env_names(&self) -> &[String] {
&self.forbidden_env_names
}
#[must_use]
pub fn forbidden_env_prefix(&self) -> &str {
&self.forbidden_env_prefix
}
}
impl Default for BridgeContext {
fn default() -> Self {
Self {
forbidden_chars: mcp_execution_core::forbidden_chars()
.iter()
.map(|c| crate::progressive::generator::sanitize_ts_string_literal(&c.to_string()))
.collect(),
forbidden_env_names: mcp_execution_core::forbidden_env_names()
.iter()
.map(|&s| s.to_string())
.collect(),
forbidden_env_prefix: mcp_execution_core::forbidden_env_prefix().to_string(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_tool_context() {
let context = ToolContext {
server_id: "github".to_string(),
name: "create_issue".to_string(),
name_literal: "create_issue".to_string(),
server_id_literal: "github".to_string(),
typescript_name: "createIssue".to_string(),
description: "Creates an issue".to_string(),
input_schema: json!({"type": "object"}),
properties: vec![],
category: Some("issues".to_string()),
keywords: Some("create,issue,new".to_string()),
short_description: "Create a new issue".to_string(),
};
assert_eq!(context.server_id, "github");
assert_eq!(context.name, "create_issue");
assert_eq!(context.typescript_name, "createIssue");
assert_eq!(context.category, Some("issues".to_string()));
assert_eq!(context.keywords, Some("create,issue,new".to_string()));
}
#[test]
fn test_property_info() {
let prop = PropertyInfo {
name: "title".to_string(),
typescript_type: "string".to_string(),
description: Some("Issue title".to_string()),
required: true,
};
assert_eq!(prop.name, "title");
assert_eq!(prop.typescript_type, "string");
assert!(prop.required);
}
#[test]
fn test_index_context() {
let context = IndexContext {
server_name: "GitHub".to_string(),
server_version: "1.0.0".to_string(),
tool_count: 5,
tools: vec![],
categories: None,
};
assert_eq!(context.server_name, "GitHub");
assert_eq!(context.tool_count, 5);
assert!(context.categories.is_none());
}
#[test]
fn test_tool_summary() {
let summary = ToolSummary {
typescript_name: "createIssue".to_string(),
description: "Creates an issue".to_string(),
category: Some("issues".to_string()),
keywords: Some("create,issue".to_string()),
short_description: Some("Create issue".to_string()),
};
assert_eq!(summary.typescript_name, "createIssue");
assert_eq!(summary.category, Some("issues".to_string()));
assert_eq!(summary.keywords, Some("create,issue".to_string()));
}
#[test]
fn test_bridge_context_default() {
let context = BridgeContext::default();
let _serialized = serde_json::to_string(&context).unwrap();
assert!(!context.forbidden_chars().is_empty());
assert!(!context.forbidden_env_names().is_empty());
assert!(!context.forbidden_env_prefix().is_empty());
}
}