Add HTTP interface, templates, generate_from_template, unified Dockerfile
Continuous Integration / Test Suite (macos-latest, nightly) (push) Has been cancelled
Continuous Integration / Test Suite (macos-latest, stable) (push) Has been cancelled
Continuous Integration / Test Suite (ubuntu-latest, 1.70.0) (push) Has been cancelled
Continuous Integration / Test Suite (ubuntu-latest, beta) (push) Has been cancelled
Continuous Integration / Test Suite (ubuntu-latest, nightly) (push) Has been cancelled
Continuous Integration / Test Suite (ubuntu-latest, stable) (push) Has been cancelled
Continuous Integration / Test Suite (windows-latest, stable) (push) Has been cancelled
Continuous Integration / Security Audit (push) Has been cancelled
Continuous Integration / Code Coverage (push) Has been cancelled
Continuous Integration / Performance Benchmarks (push) Has been cancelled
Continuous Integration / Memory Safety Check (push) Has been cancelled
Continuous Integration / Docker Build Test (push) Has been cancelled
Continuous Integration / Release Readiness (push) Has been cancelled
Continuous Integration / Integration Tests (push) Has been cancelled
Continuous Integration / Stress Testing (push) Has been cancelled
Continuous Integration / Notify Results (push) Has been cancelled
Continuous Integration / Test Suite (macos-latest, nightly) (push) Has been cancelled
Continuous Integration / Test Suite (macos-latest, stable) (push) Has been cancelled
Continuous Integration / Test Suite (ubuntu-latest, 1.70.0) (push) Has been cancelled
Continuous Integration / Test Suite (ubuntu-latest, beta) (push) Has been cancelled
Continuous Integration / Test Suite (ubuntu-latest, nightly) (push) Has been cancelled
Continuous Integration / Test Suite (ubuntu-latest, stable) (push) Has been cancelled
Continuous Integration / Test Suite (windows-latest, stable) (push) Has been cancelled
Continuous Integration / Security Audit (push) Has been cancelled
Continuous Integration / Code Coverage (push) Has been cancelled
Continuous Integration / Performance Benchmarks (push) Has been cancelled
Continuous Integration / Memory Safety Check (push) Has been cancelled
Continuous Integration / Docker Build Test (push) Has been cancelled
Continuous Integration / Release Readiness (push) Has been cancelled
Continuous Integration / Integration Tests (push) Has been cancelled
Continuous Integration / Stress Testing (push) Has been cancelled
Continuous Integration / Notify Results (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
use axum::{
|
||||
extract::{
|
||||
ws::{Message, WebSocket},
|
||||
State, WebSocketUpgrade,
|
||||
},
|
||||
response::{Html, Response},
|
||||
routing::{get, post},
|
||||
Router,
|
||||
Json,
|
||||
};
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
net::SocketAddr,
|
||||
sync::Arc,
|
||||
};
|
||||
use tower_http::cors::{Any, CorsLayer};
|
||||
use tracing::info;
|
||||
|
||||
use crate::docx_tools::DocxToolsProvider;
|
||||
|
||||
/// Application state shared across HTTP handlers
|
||||
pub struct AppState {
|
||||
pub provider: DocxToolsProvider,
|
||||
}
|
||||
|
||||
/// Request to call a tool
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ToolCallRequest {
|
||||
pub name: String,
|
||||
pub arguments: serde_json::Value,
|
||||
}
|
||||
|
||||
/// Response from a tool call
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct ToolCallResponse {
|
||||
pub success: bool,
|
||||
pub content: serde_json::Value,
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
/// Response with list of tools
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct ListToolsResponse {
|
||||
pub success: bool,
|
||||
pub tools: Vec<serde_json::Value>,
|
||||
}
|
||||
|
||||
/// Start the HTTP server
|
||||
pub async fn start_http_server(addr: &str, provider: DocxToolsProvider) -> anyhow::Result<()> {
|
||||
let state = Arc::new(AppState { provider });
|
||||
|
||||
let app = Router::new()
|
||||
.state(state.clone())
|
||||
// Serve HTML interface
|
||||
.route("/", get(index_handler))
|
||||
.route("/api/tools", get(list_tools_handler))
|
||||
.route("/api/call", post(call_tool_handler))
|
||||
.route("/ws", get(ws_handler))
|
||||
// CORS policy - allow all origins on LAN
|
||||
.layer(CorsLayer::new().allow_origin(Any()).allow_methods(tower_http::cors::Method::any()));
|
||||
|
||||
let addr = SocketAddr::from_str(addr).unwrap_or_else(|_| {
|
||||
info!("Invalid address format, using default 0.0.0.0:3000");
|
||||
"0.0.0.0:3000".parse().unwrap()
|
||||
});
|
||||
|
||||
info!("Starting HTTP server on {}", addr);
|
||||
|
||||
let listener = tokio::net::TcpListener::bind(addr).await?;
|
||||
axum::serve(listener, app).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Serve the HTML interface
|
||||
async fn index_handler() -> Html<String> {
|
||||
Html(include_str!("../assets/html_interface.html").to_string())
|
||||
}
|
||||
|
||||
/// List available tools
|
||||
async fn list_tools_handler(State(state): State<Arc<AppState>>) -> Json<ListToolsResponse> {
|
||||
let tools = state.provider.list_tools().await;
|
||||
|
||||
let tool_list: Vec<serde_json::Value> = tools.iter().map(|t| {
|
||||
serde_json::json!({
|
||||
"name": t.name,
|
||||
"description": t.description,
|
||||
"input_schema": t.input_schema
|
||||
})
|
||||
}).collect();
|
||||
|
||||
Json(ListToolsResponse {
|
||||
success: true,
|
||||
tools: tool_list,
|
||||
})
|
||||
}
|
||||
|
||||
/// Call a tool via HTTP POST
|
||||
async fn call_tool_handler(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Json(request): Json<ToolCallRequest>,
|
||||
) -> Json<ToolCallResponse> {
|
||||
let response = state.provider.call_tool(&request.name, request.arguments).await;
|
||||
|
||||
// Convert response to JSON
|
||||
let content = if let Some(content) = response.content.first() {
|
||||
match content {
|
||||
mcp_core::types::ToolResponseContent::Text(text) => {
|
||||
serde_json::from_str(&text.text).unwrap_or_else(|_| {
|
||||
serde_json::json!({"text": text.text.clone()})
|
||||
})
|
||||
},
|
||||
mcp_core::types::ToolResponseContent::Image(image) => {
|
||||
serde_json::json!({
|
||||
"data": image.data,
|
||||
"mimeType": image.mime_type
|
||||
})
|
||||
},
|
||||
}
|
||||
} else {
|
||||
serde_json::json!({})
|
||||
};
|
||||
|
||||
Json(ToolCallResponse {
|
||||
success: response.is_error.unwrap_or(false) == false,
|
||||
content,
|
||||
error: response.is_error.unwrap_or(false).then(|| "Tool call failed".to_string()),
|
||||
})
|
||||
}
|
||||
|
||||
/// WebSocket handler for real-time communication
|
||||
async fn ws_handler(
|
||||
ws: WebSocketUpgrade,
|
||||
State(state): State<Arc<AppState>>
|
||||
) -> Result<Response, axum::http::StatusCode> {
|
||||
ws.on_upgrade(move |socket| async move {
|
||||
let provider = state.provider.clone();
|
||||
let mut ws = socket;
|
||||
|
||||
// Handle WebSocket messages
|
||||
while let Some(msg) = ws.recv().await {
|
||||
let msg = match msg {
|
||||
Ok(msg) => msg,
|
||||
Err(_) => continue,
|
||||
};
|
||||
|
||||
let text = match msg {
|
||||
Message::Text(text) => text.to_string(),
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
// Parse request
|
||||
let request: ToolCallRequest = match serde_json::from_str(&text) {
|
||||
Ok(req) => req,
|
||||
Err(e) => {
|
||||
let error_response = ToolCallResponse {
|
||||
success: false,
|
||||
content: serde_json::json!({}),
|
||||
error: Some(format!("Parse error: {}", e)),
|
||||
};
|
||||
let _ = ws.send(Message::Text(
|
||||
serde_json::to_string(&error_response).unwrap_or("{}".to_string())
|
||||
)).await;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
// Call tool
|
||||
let response = provider.call_tool(&request.name, request.arguments).await;
|
||||
|
||||
// Convert response to JSON
|
||||
let content = if let Some(content) = response.content.first() {
|
||||
match content {
|
||||
mcp_core::types::ToolResponseContent::Text(text) => {
|
||||
serde_json::from_str(&text.text).unwrap_or_else(|_| {
|
||||
serde_json::json!({"text": text.text.clone()})
|
||||
})
|
||||
},
|
||||
mcp_core::types::ToolResponseContent::Image(image) => {
|
||||
serde_json::json!({
|
||||
"data": image.data,
|
||||
"mimeType": image.mime_type
|
||||
})
|
||||
},
|
||||
}
|
||||
} else {
|
||||
serde_json::json!({})
|
||||
};
|
||||
|
||||
let ws_response = ToolCallResponse {
|
||||
success: response.is_error.unwrap_or(false) == false,
|
||||
content,
|
||||
error: response.is_error.unwrap_or(false).then(|| "Tool call failed".to_string()),
|
||||
};
|
||||
|
||||
let _ = ws.send(Message::Text(
|
||||
serde_json::to_string(&ws_response).unwrap_or("{}".to_string())
|
||||
)).await;
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user