use anyhow::Result;
use std::time::Duration;
use tokio::net::TcpListener;
use crate::{
mcp_start_task,
model::{McpConfig, McpProtocol, McpType},
proxy::{McpClientConfig, SseClientConnection},
};
fn get_coze_config() -> Result<String> {
let plugin_id = std::env::var("COZE_PLUGIN_ID")
.map_err(|_| anyhow::anyhow!("COZE_PLUGIN_ID environment variable not set"))?;
let bearer_token = std::env::var("COZE_BEARER_TOKEN")
.map_err(|_| anyhow::anyhow!("COZE_BEARER_TOKEN environment variable not set"))?;
let config = r#"{
"mcpServers": {
"coze_plugin_tianyancha": {
"url": "https://mcp.coze.cn/v1/plugins/PLUGIN_ID",
"headers": {
"Authorization": "Bearer BEARER_TOKEN"
}
}
}
}"#;
Ok(config
.replace("PLUGIN_ID", &plugin_id)
.replace("BEARER_TOKEN", &bearer_token))
}
#[tokio::test]
#[ignore] async fn test_coze_streamable_to_sse_proxy() -> Result<()> {
let _ = tracing_subscriber::fmt().with_test_writer().try_init();
println!("๐งช Starting Coze MCP test: Streamable HTTP -> SSE conversion");
let coze_config = get_coze_config()?;
let mcp_config = McpConfig::from_json_with_server(
"coze_plugin_tianyancha".to_string(),
coze_config,
McpType::OneShot,
McpProtocol::Sse, )?;
println!("โ
Configuration created with SSE client protocol");
let (router, ct) = mcp_start_task(mcp_config).await?;
println!("โ
MCP service started");
let listener = TcpListener::bind("127.0.0.1:0").await?;
let port = listener.local_addr()?.port();
println!("โ
HTTP server listening on 127.0.0.1:{}", port);
let server_handle = tokio::spawn(async move {
axum::serve(listener, router.into_make_service())
.await
.expect("Server error");
});
tokio::time::sleep(Duration::from_secs(3)).await;
println!("โ
Backend ready");
let sse_url = format!(
"http://127.0.0.1:{}/mcp/sse/proxy/coze_plugin_tianyancha/sse",
port
);
let client_config = McpClientConfig::new(sse_url.to_string());
let conn = tokio::time::timeout(
Duration::from_secs(30),
SseClientConnection::connect(client_config.clone()),
)
.await
.map_err(|_| anyhow::anyhow!("SSE connection timeout (30s)"))?
.map_err(|e| anyhow::anyhow!("SSE connection failed: {}", e))?;
println!("โ
SSE client connected to {}", sse_url);
let tools = tokio::time::timeout(Duration::from_secs(30), conn.list_tools())
.await
.map_err(|_| anyhow::anyhow!("list_tools timeout (30s)"))?
.map_err(|e| anyhow::anyhow!("list_tools failed: {}", e))?;
println!("๐ Received tools/list response: {} tools", tools.len());
if tools.is_empty() {
println!("โ ๏ธ Warning: tools/list returned empty array");
} else {
println!("โ
Found {} tools:", tools.len());
for tool in &tools {
let desc = tool.description.as_deref().unwrap_or("no description");
println!(" - {} : {}", tool.name, desc);
}
}
for tool in &tools {
assert!(!tool.name.is_empty(), "Tool name should not be empty");
println!(" โ Tool '{}' has valid structure", tool.name);
}
ct.cancel();
server_handle.abort();
println!("๐งน Cleanup complete");
Ok(())
}