Skip to content

Commit 6d1a262

Browse files
committed
feat: 控制台增加mcp服务导出接口; #272
1 parent effad66 commit 6d1a262

4 files changed

Lines changed: 157 additions & 2 deletions

File tree

src/console/api.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,10 @@ pub fn console_api_config_v2(config: &mut web::ServiceConfig) {
404404
.service(
405405
web::resource("/mcp/server/publish/history")
406406
.route(web::post().to(v2::mcp_server_api::publish_history_mcp_server)),
407+
)
408+
.service(
409+
web::resource("/mcp/server/download")
410+
.route(web::get().to(v2::mcp_server_api::download_mcp_servers)),
407411
),
408412
);
409413
}

src/console/model/mcp_server_model.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,3 +338,51 @@ impl McpServerValueDto {
338338
}
339339
}
340340
}
341+
342+
/// McpServer导入导出DTO,用于YAML序列化
343+
#[derive(Clone, Debug, Serialize, Deserialize)]
344+
#[serde(rename_all = "camelCase")]
345+
pub struct McpServerImportDto {
346+
pub unique_key: String,
347+
pub name: String,
348+
pub description: String,
349+
pub auth_keys: Vec<String>,
350+
pub tools: Vec<McpToolImportDto>,
351+
}
352+
353+
/// McpTool导入导出DTO,用于YAML序列化
354+
#[derive(Clone, Debug, Serialize, Deserialize)]
355+
#[serde(rename_all = "camelCase")]
356+
pub struct McpToolImportDto {
357+
pub tool_name: String,
358+
pub tool_group: String,
359+
pub route_rule: ToolRouteRule,
360+
}
361+
362+
impl From<&crate::mcp::model::mcp::McpServerDto> for McpServerImportDto {
363+
fn from(server: &crate::mcp::model::mcp::McpServerDto) -> Self {
364+
let tools = if let Some(ref current_value) = server.current_value {
365+
current_value.tools.iter().map(|tool| McpToolImportDto {
366+
tool_name: tool.tool_key.tool_name.as_str().to_string(),
367+
tool_group: tool.tool_key.group.as_str().to_string(),
368+
route_rule: tool.route_rule.clone(),
369+
}).collect()
370+
} else if let Some(ref release_value) = server.release_value {
371+
release_value.tools.iter().map(|tool| McpToolImportDto {
372+
tool_name: tool.tool_key.tool_name.as_str().to_string(),
373+
tool_group: tool.tool_key.group.as_str().to_string(),
374+
route_rule: tool.route_rule.clone(),
375+
}).collect()
376+
} else {
377+
Vec::new()
378+
};
379+
380+
Self {
381+
unique_key: server.unique_key.as_str().to_string(),
382+
name: server.name.as_str().to_string(),
383+
description: server.description.as_str().to_string(),
384+
auth_keys: server.auth_keys.iter().map(|key| key.as_str().to_string()).collect(),
385+
tools,
386+
}
387+
}
388+
}

src/console/v2/mcp_server_api.rs

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@ use crate::mcp::model::mcp::McpServerDto;
1515
use crate::raft::store::{ClientRequest, ClientResponse};
1616
use crate::sequence::{SequenceRequest, SequenceResult};
1717
use actix_web::{web, HttpMessage, HttpRequest, HttpResponse, Responder};
18+
use std::fs::File;
19+
use std::io::{Read, Seek, SeekFrom, Write};
1820
use std::sync::Arc;
21+
use zip::write::FileOptions;
22+
use zip::ZipWriter;
1923

2024
/// 查询McpServer列表
2125
pub async fn query_mcp_server_list(
@@ -460,3 +464,102 @@ async fn do_publish_history_mcp_server(
460464
)),
461465
}
462466
}
467+
468+
/// 批量导出McpServer
469+
pub async fn download_mcp_servers(
470+
_req: HttpRequest,
471+
request: web::Query<McpServerQueryRequest>,
472+
appdata: web::Data<Arc<AppShareData>>,
473+
) -> impl Responder {
474+
// 验证查询参数
475+
if let Err(err) = request.validate() {
476+
return handle_param_error(err, "McpServer download parameter validation failed");
477+
}
478+
479+
// 转换查询参数,设置最大限制10万
480+
let mut query_param = request.to_mcp_query_param();
481+
query_param.limit = 100_000;
482+
query_param.offset = 0;
483+
484+
// 发送查询请求到MCP Manager
485+
let cmd = McpManagerReq::QueryServer(query_param);
486+
match appdata.mcp_manager.send(cmd).await {
487+
Ok(res) => match res {
488+
Ok(McpManagerResult::ServerPageInfo(_, list)) => {
489+
let mut tmpfile: File = tempfile::tempfile().unwrap();
490+
{
491+
let write = std::io::Write::by_ref(&mut tmpfile);
492+
let zip = ZipWriter::new(write);
493+
generate_mcp_server_zip(zip, list).ok();
494+
}
495+
// Seek to start
496+
tmpfile.seek(SeekFrom::Start(0)).unwrap();
497+
let mut buf = vec![];
498+
tmpfile.read_to_end(&mut buf).unwrap();
499+
500+
let filename = format!("rnacos_mcserver_export_{}.zip", crate::now_millis());
501+
HttpResponse::Ok()
502+
.insert_header(actix_web::http::header::ContentType::octet_stream())
503+
.insert_header(actix_web::http::header::ContentDisposition::attachment(
504+
filename,
505+
))
506+
.body(buf)
507+
}
508+
Ok(_) => handle_unexpected_response_error("MCP Manager download McpServer"),
509+
Err(err) => handle_mcp_manager_error(err, "download McpServer"),
510+
},
511+
Err(err) => handle_system_error(
512+
format!("Unable to connect to MCP Manager: {}", err),
513+
"Failed to send download request to MCP Manager",
514+
),
515+
}
516+
}
517+
518+
/// 生成McpServer的zip文件
519+
fn generate_mcp_server_zip(
520+
mut zip: ZipWriter<&mut File>,
521+
list: Vec<McpServerDto>,
522+
) -> anyhow::Result<()> {
523+
if list.is_empty() {
524+
let options = FileOptions::default()
525+
.compression_method(zip::CompressionMethod::Stored)
526+
.unix_permissions(0o755);
527+
zip.start_file(".ignore", options)?;
528+
zip.write_all("empty mcpservers".as_bytes())?;
529+
}
530+
531+
for item in &list {
532+
// 生成YAML内容
533+
let yaml_content = generate_mcp_server_yaml(item);
534+
535+
// 文件名格式: {unique_key}.yaml
536+
let filename = format!("{}.yaml", item.unique_key.as_str());
537+
538+
let options = FileOptions::default()
539+
.compression_method(zip::CompressionMethod::Stored)
540+
.unix_permissions(0o755);
541+
542+
zip.start_file(filename, options)?;
543+
zip.write_all(yaml_content.as_bytes())?;
544+
}
545+
546+
zip.finish()?;
547+
Ok(())
548+
}
549+
550+
/// 生成McpServer的YAML内容
551+
fn generate_mcp_server_yaml(mcp_server: &McpServerDto) -> String {
552+
use crate::console::model::mcp_server_model::McpServerImportDto;
553+
554+
// 转换为McpServerImportDto
555+
let import_dto = McpServerImportDto::from(mcp_server);
556+
557+
// 使用serde_yml序列化
558+
serde_yml::to_string(&import_dto).unwrap_or_else(|_| {
559+
// 如果序列化失败,返回基本错误信息
560+
format!(
561+
"Failed to serialize McpServer: {}",
562+
mcp_server.unique_key.as_str()
563+
)
564+
})
565+
}

src/mcp/model/tools.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ pub struct ToolRouteRule {
328328
pub method: Arc<String>,
329329
pub addition_headers: std::collections::HashMap<String, Arc<String>>,
330330
pub convert_type: ConvertType,
331-
pub service_namespace: Arc<String>,
331+
pub service_namespace: Option<Arc<String>>,
332332
pub service_group: Arc<String>,
333333
pub service_name: Arc<String>,
334334
}
@@ -341,7 +341,7 @@ impl Default for ToolRouteRule {
341341
method: Arc::new("GET".to_string()),
342342
addition_headers: std::collections::HashMap::new(),
343343
convert_type: ConvertType::None,
344-
service_namespace: EMPTY_ARC_STRING.clone(),
344+
service_namespace: None,
345345
service_group: EMPTY_ARC_STRING.clone(),
346346
service_name: EMPTY_ARC_STRING.clone(),
347347
}

0 commit comments

Comments
 (0)