a3s_code_core/mcp/
projection.rs1use std::fmt;
4use std::sync::Arc;
5
6use async_trait::async_trait;
7use tokio_util::sync::CancellationToken;
8
9use super::manager::connect_ready_client;
10use super::{McpBinding, McpClient, McpServerConfig};
11use crate::capability::{
12 CapabilityAdapterError, CapabilityEffect, CapabilityEffectError, CapabilityProjectionAdapter,
13 CapabilityValue, PreparedCapability,
14};
15
16pub struct McpProjectionAdapter {
24 config: McpServerConfig,
25}
26
27impl McpProjectionAdapter {
28 pub fn new(config: McpServerConfig) -> Self {
29 Self { config }
30 }
31}
32
33impl fmt::Debug for McpProjectionAdapter {
34 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
35 formatter
38 .debug_struct("McpProjectionAdapter")
39 .field("server_name", &self.config.name)
40 .finish_non_exhaustive()
41 }
42}
43
44#[async_trait]
45impl CapabilityProjectionAdapter for McpProjectionAdapter {
46 async fn prepare(
47 self: Box<Self>,
48 cancellation: CancellationToken,
49 ) -> std::result::Result<PreparedCapability, CapabilityAdapterError> {
50 if cancellation.is_cancelled() {
51 return Err(CapabilityAdapterError::new(
52 "MCP projection preparation was cancelled",
53 ));
54 }
55
56 let (client, tools) = connect_ready_client(&self.config)
57 .await
58 .map_err(|error| CapabilityAdapterError::new(error.to_string()))?;
59 if cancellation.is_cancelled() {
60 close_after_failed_prepare(&self.config.name, &client).await;
61 return Err(CapabilityAdapterError::new(
62 "MCP projection preparation was cancelled",
63 ));
64 }
65
66 let binding = match McpBinding::new(&self.config.name, Arc::clone(&client), tools) {
67 Ok(binding) => Arc::new(binding),
68 Err(error) => {
69 close_after_failed_prepare(&self.config.name, &client).await;
70 return Err(CapabilityAdapterError::new(error.to_string()));
71 }
72 };
73 let mut prepared = PreparedCapability::new(CapabilityValue::Mcp(binding));
74 prepared.push_effect(McpConnectionEffect {
75 server_name: self.config.name.into_boxed_str(),
76 client,
77 })?;
78 Ok(prepared)
79 }
80}
81
82struct McpConnectionEffect {
83 server_name: Box<str>,
84 client: Arc<McpClient>,
85}
86
87#[async_trait]
88impl CapabilityEffect for McpConnectionEffect {
89 fn name(&self) -> &str {
90 "mcp.projected.connection"
91 }
92
93 async fn close(self: Box<Self>) -> std::result::Result<(), CapabilityEffectError> {
94 self.client.close().await.map_err(|error| {
95 CapabilityEffectError::new(format!(
96 "Failed to close projected MCP server '{}': {error}",
97 self.server_name
98 ))
99 })
100 }
101}
102
103async fn close_after_failed_prepare(server_name: &str, client: &McpClient) {
104 if let Err(error) = client.close().await {
105 tracing::warn!(
106 server = %server_name,
107 error = %error,
108 "Failed to close MCP connection after projection preparation failed"
109 );
110 }
111}