use dashmap::DashMap;
use futures::future::try_join_all;
use indexmap::IndexMap;
use std::sync::Arc;
use objectiveai_sdk::laboratories::Laboratory;
use objectiveai_sdk::mcp::{
JsonRpcNotification,
resource::{ListResourcesResult, ReadResourceResult, Resource},
server::{ListServersResult, Server},
tool::{
CallToolRequestParams, CallToolResult, ContentBlock, ListToolsResult,
TextContent, Tool, ToolSchemaObject, ToolSchemaType,
},
};
use crate::reverse_channel::Upstream;
pub const LABORATORY_TRANSFER_TOOL: &str = "laboratory_transfer";
use axum::http::HeaderMap;
use tokio::sync::{RwLock, broadcast};
use tokio_util::sync::CancellationToken;
const OUTBOUND_CAPACITY: usize = 64;
fn request_id_key(id: &serde_json::Value) -> String {
serde_json::to_string(id).unwrap_or_default()
}
#[derive(Debug)]
pub struct Session {
pub connections: IndexMap<String, Upstream>,
pub outbound: broadcast::Sender<JsonRpcNotification>,
in_flight: DashMap<String, CancellationToken>,
pub transient_headers: RwLock<IndexMap<String, String>>,
}
impl Session {
pub(crate) fn new(
connections: IndexMap<String, Upstream>,
) -> Self {
let (outbound, _) = broadcast::channel(OUTBOUND_CAPACITY);
for connection in connections.values() {
let tx = outbound.clone();
connection.set_on_tools_list_changed(move || {
let _ = tx.send(JsonRpcNotification {
jsonrpc: "2.0".into(),
method: "notifications/tools/list_changed".into(),
params: None,
});
});
let tx = outbound.clone();
connection.set_on_resources_list_changed(move || {
let _ = tx.send(JsonRpcNotification {
jsonrpc: "2.0".into(),
method: "notifications/resources/list_changed".into(),
params: None,
});
});
}
Self {
connections,
outbound,
in_flight: DashMap::new(),
transient_headers: RwLock::new(IndexMap::new()),
}
}
pub const TRANSIENT_HEADER_KEYS: [&'static str; 6] = [
"X-OBJECTIVEAI-RESPONSE-ID",
"X-OBJECTIVEAI-RESPONSE-IDS",
"X-OBJECTIVEAI-AGENT-INSTANCE-HIERARCHY",
"X-OBJECTIVEAI-AGENT-ID",
"X-OBJECTIVEAI-AGENT-FULL-ID",
"X-OBJECTIVEAI-AGENT-REMOTE",
];
pub async fn apply_transient_headers(&self, src: &HeaderMap) {
let mut bag = IndexMap::new();
for key in Self::TRANSIENT_HEADER_KEYS {
if let Some(v) = src.get(key).and_then(|v| v.to_str().ok()) {
bag.insert(key.to_string(), v.to_string());
}
}
let snapshot = bag.clone();
*self.transient_headers.write().await = bag;
for connection in self.connections.values() {
connection.set_extra_headers(snapshot.clone()).await;
}
}
pub fn register_in_flight(&self, id: &serde_json::Value) -> CancellationToken {
let token = CancellationToken::new();
self.in_flight.insert(request_id_key(id), token.clone());
token
}
pub fn deregister_in_flight(&self, id: &serde_json::Value) {
self.in_flight.remove(&request_id_key(id));
}
pub fn cancel_in_flight(&self, id: &serde_json::Value) -> bool {
match self.in_flight.get(&request_id_key(id)) {
Some(entry) => {
entry.value().cancel();
true
}
None => false,
}
}
pub async fn list_tools(&self) -> Result<ListToolsResult, Arc<objectiveai_sdk::mcp::Error>> {
self.list_tools_filtered(None, None).await
}
pub async fn list_tools_filtered(
&self,
filter_url: Option<&str>,
filter_name: Option<&str>,
) -> Result<ListToolsResult, Arc<objectiveai_sdk::mcp::Error>> {
let pairs: Vec<(&String, &Upstream)> = self
.connections
.iter()
.filter(|(name, c)| {
filter_url.is_none_or(|u| c.url() == u)
&& filter_name.is_none_or(|n| name.as_str() == n)
})
.collect();
let results = try_join_all(
pairs
.iter()
.map(|(_, c)| async move {
let r = c.list_tools().await;
r
}),
)
.await?;
let mut tools: Vec<Tool> = Vec::new();
for ((server_name, _), arc) in pairs.into_iter().zip(results) {
for tool in arc.iter() {
let mut prefixed = tool.clone();
prefixed.name = prefix_name(server_name, &tool.name);
tools.push(prefixed);
}
}
if filter_url.is_none()
&& filter_name.is_none()
&& self.laboratory_count() >= 2
{
tools.push(laboratory_transfer_tool());
}
tools.sort_by(|a, b| a.name.cmp(&b.name));
Ok(ListToolsResult {
tools,
next_cursor: None,
_meta: None,
})
}
pub fn list_servers(&self) -> ListServersResult {
let mut servers: Vec<Server> = self
.connections
.iter()
.map(|(prefix, up)| Server {
name: prefix.clone(),
url: up.url().to_string(),
initialize_result: up.initialize_result().clone(),
laboratory: up.laboratory(),
plugin: up.plugin(),
})
.collect();
servers.sort_by(|a, b| a.name.cmp(&b.name));
ListServersResult { servers }
}
pub async fn list_resources(&self) -> Result<ListResourcesResult, Arc<objectiveai_sdk::mcp::Error>> {
self.list_resources_filtered(None, None).await
}
pub async fn list_resources_filtered(
&self,
filter_url: Option<&str>,
filter_name: Option<&str>,
) -> Result<ListResourcesResult, Arc<objectiveai_sdk::mcp::Error>> {
let pairs: Vec<(&String, &Upstream)> = self
.connections
.iter()
.filter(|(name, c)| {
filter_url.is_none_or(|u| c.url() == u)
&& filter_name.is_none_or(|n| name.as_str() == n)
})
.collect();
let results = try_join_all(
pairs
.iter()
.map(|(_, c)| async move {
let r = c.list_resources().await;
r
}),
)
.await?;
let mut resources: Vec<Resource> = Vec::new();
for ((server_name, _), arc) in pairs.into_iter().zip(results) {
for resource in arc.iter() {
let mut prefixed = resource.clone();
prefixed.uri = prefix_name(server_name, &resource.uri);
resources.push(prefixed);
}
}
resources.sort_by(|a, b| a.uri.cmp(&b.uri));
Ok(ListResourcesResult {
resources,
next_cursor: None,
_meta: None,
})
}
pub async fn call_tool(
&self,
params: &CallToolRequestParams,
) -> Result<CallToolResult, CallToolError> {
if params.name == LABORATORY_TRANSFER_TOOL {
return Ok(self.laboratory_transfer(params).await);
}
let (connection, original_name) = self
.route(¶ms.name)
.ok_or_else(|| CallToolError::ToolNotFound(params.name.clone()))?;
let upstream_params = CallToolRequestParams {
name: original_name,
arguments: params.arguments.clone(),
task: params.task.clone(),
_meta: params._meta.clone(),
};
let r = connection.call_tool(&upstream_params).await;
Ok(r?)
}
fn laboratory_count(&self) -> usize {
self.connections
.values()
.filter(|u| u.laboratory().is_some())
.count()
}
fn find_laboratory(&self, id: &str) -> Option<&Upstream> {
self.connections.values().find(|u| {
matches!(u.laboratory(), Some(Laboratory::Client(c)) if c.id == id)
})
}
async fn laboratory_transfer(&self, params: &CallToolRequestParams) -> CallToolResult {
let arg = |key: &str| -> Option<&str> {
params
.arguments
.as_ref()
.and_then(|m| m.get(key))
.and_then(|v| v.as_str())
};
let (source, source_path, destination, destination_path) = match (
arg("source"),
arg("source_path"),
arg("destination"),
arg("destination_path"),
) {
(Some(s), Some(sp), Some(d), Some(dp)) => (s, sp, d, dp),
_ => {
return transfer_error(
"laboratory_transfer requires string arguments: source, \
source_path, destination, destination_path",
);
}
};
if source == destination {
return transfer_error("source and destination must be different laboratories");
}
if self.find_laboratory(source).is_none() {
return transfer_error(format!("no laboratory with id '{source}' in this session"));
}
let channel = match self.find_laboratory(destination) {
Some(u) => match u.reverse_channel() {
Some(c) => c,
None => return transfer_error("destination laboratory has no reverse channel"),
},
None => {
return transfer_error(format!(
"no laboratory with id '{destination}' in this session"
));
}
};
match channel
.transfer_laboratories(
source.to_string(),
destination.to_string(),
source_path.to_string(),
destination_path.to_string(),
)
.await
{
Ok(result) => transfer_text(format!(
"transferred {} bytes: {source}:{source_path} → {destination}:{destination_path}",
result.bytes
)),
Err(e) => transfer_error(format!("transfer failed: {e}")),
}
}
pub async fn read_resource(
&self,
uri: &str,
) -> Result<ReadResourceResult, ReadResourceError> {
let (connection, original_uri) = self
.route(uri)
.ok_or_else(|| ReadResourceError::ResourceNotFound(uri.to_string()))?;
let r = connection.read_resource(&original_uri).await;
Ok(r?)
}
fn route<'a>(&'a self, prefixed: &str) -> Option<(&'a Upstream, String)> {
let (prefix, rest) = prefixed.split_once('_')?;
let connection = self.connections.get(prefix)?;
Some((connection, rest.to_string()))
}
}
fn prefix_name(server_name: &str, name: &str) -> String {
format!("{server_name}_{name}")
}
fn laboratory_transfer_tool() -> Tool {
fn string_prop(description: &str) -> serde_json::Value {
serde_json::json!({ "type": "string", "description": description })
}
let mut properties: IndexMap<String, serde_json::Value> = IndexMap::new();
properties.insert(
"source".to_string(),
string_prop(
"Laboratory id of the source (the `laboratory.id` from \
`agents mcp servers list`).",
),
);
properties.insert(
"source_path".to_string(),
string_prop("Absolute path of the file or folder to copy from the source laboratory."),
);
properties.insert(
"destination".to_string(),
string_prop("Laboratory id of the destination."),
);
properties.insert(
"destination_path".to_string(),
string_prop(
"Absolute destination directory in the destination laboratory; the \
source's basename is created inside it.",
),
);
Tool {
name: LABORATORY_TRANSFER_TOOL.to_string(),
title: Some("Laboratory Transfer".to_string()),
description: Some(
"Copy a file or folder from one laboratory to another (streamed). \
Identify the laboratories by their id from `agents mcp servers list`."
.to_string(),
),
icons: None,
input_schema: ToolSchemaObject {
r#type: ToolSchemaType::Object,
properties: Some(properties),
required: Some(vec![
"source".to_string(),
"source_path".to_string(),
"destination".to_string(),
"destination_path".to_string(),
]),
extra: IndexMap::new(),
},
output_schema: None,
annotations: None,
execution: None,
_meta: None,
}
}
fn transfer_text(text: String) -> CallToolResult {
CallToolResult {
content: vec![ContentBlock::Text(TextContent {
text,
annotations: None,
_meta: None,
})],
structured_content: None,
is_error: None,
_meta: None,
}
}
fn transfer_error(text: impl Into<String>) -> CallToolResult {
CallToolResult {
content: vec![ContentBlock::Text(TextContent {
text: text.into(),
annotations: None,
_meta: None,
})],
structured_content: None,
is_error: Some(true),
_meta: None,
}
}
#[derive(Debug, thiserror::Error)]
pub enum CallToolError {
#[error("tool not found on any upstream: {0}")]
ToolNotFound(String),
#[error("upstream call_tool failed: {0}")]
Upstream(#[from] objectiveai_sdk::mcp::Error),
}
#[derive(Debug, thiserror::Error)]
pub enum ReadResourceError {
#[error("resource not found on any upstream: {0}")]
ResourceNotFound(String),
#[error("upstream read_resource failed: {0}")]
Upstream(#[from] objectiveai_sdk::mcp::Error),
}