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:
+224
-1
@@ -20,6 +20,7 @@ pub struct DocxToolsProvider {
|
||||
advanced: Arc<AdvancedDocxHandler>,
|
||||
security: Arc<SecurityMiddleware>,
|
||||
security_config: SecurityConfig,
|
||||
templates_dir: PathBuf,
|
||||
}
|
||||
|
||||
impl DocxToolsProvider {
|
||||
@@ -28,6 +29,10 @@ impl DocxToolsProvider {
|
||||
}
|
||||
|
||||
pub fn new_with_security(security_config: SecurityConfig) -> Self {
|
||||
Self::new_with_security_and_templates(security_config, PathBuf::from("/templates"))
|
||||
}
|
||||
|
||||
pub fn new_with_security_and_templates(security_config: SecurityConfig, templates_dir: PathBuf) -> Self {
|
||||
Self {
|
||||
handler: Arc::new(RwLock::new(DocxHandler::new().expect("Failed to create DocxHandler"))),
|
||||
converter: Arc::new(DocumentConverter::new()),
|
||||
@@ -35,6 +40,7 @@ impl DocxToolsProvider {
|
||||
advanced: Arc::new(AdvancedDocxHandler::new()),
|
||||
security: Arc::new(SecurityMiddleware::new(security_config.clone())),
|
||||
security_config,
|
||||
templates_dir,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +58,7 @@ impl DocxToolsProvider {
|
||||
advanced: Arc::new(AdvancedDocxHandler::new()),
|
||||
security: Arc::new(SecurityMiddleware::new(security_config.clone())),
|
||||
security_config,
|
||||
templates_dir: PathBuf::from("/templates"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -956,6 +963,56 @@ impl DocxToolsProvider {
|
||||
}),
|
||||
annotations: None,
|
||||
},
|
||||
Tool {
|
||||
name: "list_templates".to_string(),
|
||||
description: Some("List available document templates from the templates directory".to_string()),
|
||||
input_schema: json!({
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"required": []
|
||||
}),
|
||||
annotations: None,
|
||||
},
|
||||
Tool {
|
||||
name: "open_template".to_string(),
|
||||
description: Some("Open a template document by name from the templates directory".to_string()),
|
||||
input_schema: json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Template file name (e.g., 'nda_template.docx')"
|
||||
}
|
||||
},
|
||||
"required": ["name"]
|
||||
}),
|
||||
annotations: None,
|
||||
},
|
||||
Tool {
|
||||
name: "generate_from_template".to_string(),
|
||||
description: Some(
|
||||
"Generate a new document from a template by filling placeholders like {{FIELD_NAME}} with provided values".to_string()
|
||||
),
|
||||
input_schema: json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"template_name": {
|
||||
"type": "string",
|
||||
"description": "Template file name (e.g., 'nda_template.docx')"
|
||||
},
|
||||
"output_path": {
|
||||
"type": "string",
|
||||
"description": "Output DOCX path (e.g., '/out/nda_filled.docx')"
|
||||
},
|
||||
"fields": {
|
||||
"type": "object",
|
||||
"description": "Key-value pairs; keys are placeholder names without braces. Example: {\"CLIENT_NAME\": \"Acme Corp\"}"
|
||||
}
|
||||
},
|
||||
"required": ["template_name", "output_path", "fields"]
|
||||
}),
|
||||
annotations: None,
|
||||
},
|
||||
];
|
||||
|
||||
// Filter tools based on security configuration
|
||||
@@ -1715,7 +1772,173 @@ impl DocxToolsProvider {
|
||||
Err(e) => ToolOutcome::Error { code: ErrorCode::InternalError, error: e.to_string(), hint: None },
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
"list_templates" => {
|
||||
let mut templates = Vec::new();
|
||||
if self.templates_dir.exists() {
|
||||
if let Ok(entries) = std::fs::read_dir(&self.templates_dir) {
|
||||
for entry in entries.filter_map(|e| e.ok()) {
|
||||
let path = entry.path();
|
||||
if path.is_file() {
|
||||
if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
|
||||
if ext.eq_ignore_ascii_case("docx") {
|
||||
if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
|
||||
templates.push(name.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
templates.sort();
|
||||
ToolOutcome::Metadata {
|
||||
metadata: serde_json::json!({ "templates": templates }),
|
||||
}
|
||||
},
|
||||
|
||||
"open_template" => {
|
||||
let name = arguments["name"].as_str().unwrap_or("");
|
||||
if name.is_empty() {
|
||||
ToolOutcome::Error {
|
||||
code: ErrorCode::ValidationError,
|
||||
error: "Template name is required".to_string(),
|
||||
hint: Some("Provide 'name' with a .docx filename from list_templates".to_string()),
|
||||
}
|
||||
} else {
|
||||
let path = self.templates_dir.join(name);
|
||||
if !path.exists() || !path.is_file() {
|
||||
ToolOutcome::Error {
|
||||
code: ErrorCode::ValidationError,
|
||||
error: format!("Template not found: {}", name),
|
||||
hint: Some("Check list_templates for available names".to_string()),
|
||||
}
|
||||
} else {
|
||||
let mut handler = self.handler.write().unwrap();
|
||||
match handler.open_document(&path) {
|
||||
Ok(doc_id) => ToolOutcome::Created {
|
||||
document_id: doc_id,
|
||||
message: Some(format!("Opened template '{}' as document", name)),
|
||||
},
|
||||
Err(e) => ToolOutcome::Error {
|
||||
code: ErrorCode::InternalError,
|
||||
error: e.to_string(),
|
||||
hint: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"generate_from_template" => {
|
||||
let template_name = arguments["template_name"].as_str().unwrap_or("");
|
||||
let output_path = arguments["output_path"].as_str().unwrap_or("");
|
||||
let fields = arguments.get("fields").and_then(|v| v.as_object()).cloned().unwrap_or_default();
|
||||
|
||||
if template_name.is_empty() {
|
||||
ToolOutcome::Error {
|
||||
code: ErrorCode::ValidationError,
|
||||
error: "template_name is required".to_string(),
|
||||
hint: Some("Provide the template file name from list_templates".to_string()),
|
||||
}
|
||||
} else if output_path.is_empty() {
|
||||
ToolOutcome::Error {
|
||||
code: ErrorCode::ValidationError,
|
||||
error: "output_path is required".to_string(),
|
||||
hint: Some("Provide an absolute path where the generated DOCX will be saved".to_string()),
|
||||
}
|
||||
} else {
|
||||
let template_path = self.templates_dir.join(template_name);
|
||||
if !template_path.exists() || !template_path.is_file() {
|
||||
ToolOutcome::Error {
|
||||
code: ErrorCode::ValidationError,
|
||||
error: format!("Template not found: {}", template_name),
|
||||
hint: Some("Check list_templates for available names".to_string()),
|
||||
}
|
||||
} else {
|
||||
// Open template
|
||||
let mut handler = self.handler.write().unwrap();
|
||||
let doc_id = match handler.open_document(&template_path) {
|
||||
Ok(id) => id,
|
||||
Err(e) => {
|
||||
drop(handler);
|
||||
return ToolOutcome::Error {
|
||||
code: ErrorCode::InternalError,
|
||||
error: e.to_string(),
|
||||
hint: None,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// Apply field replacements
|
||||
let mut replace_count = 0usize;
|
||||
for (key, value) in &fields {
|
||||
let placeholder = format!("{{{{{}}}}}", key);
|
||||
let val_str = match value {
|
||||
Value::String(s) => s.clone(),
|
||||
_ => value.to_string(),
|
||||
};
|
||||
if let Ok(count) = handler.find_and_replace_advanced(
|
||||
&doc_id,
|
||||
&placeholder,
|
||||
&val_str,
|
||||
false, // case_sensitive: false for placeholders
|
||||
true, // whole_word: true (treat placeholder as whole token)
|
||||
false, // use_regex: false
|
||||
) {
|
||||
replace_count += count;
|
||||
}
|
||||
}
|
||||
|
||||
// Save generated document
|
||||
let out_path = PathBuf::from(output_path);
|
||||
let result = if out_path.parent().is_some() {
|
||||
if let Err(e) = std::fs::create_dir_all(out_path.parent().unwrap()) {
|
||||
drop(handler);
|
||||
ToolOutcome::Error {
|
||||
code: ErrorCode::InternalError,
|
||||
error: format!("Failed to create output directory: {}", e),
|
||||
hint: None,
|
||||
}
|
||||
} else {
|
||||
match handler.save_document(&doc_id, &out_path) {
|
||||
Ok(()) => ToolOutcome::Ok {
|
||||
message: Some(format!(
|
||||
"Generated document from template '{}' with {} replacements at {}",
|
||||
template_name, replace_count, output_path
|
||||
)),
|
||||
},
|
||||
Err(e) => ToolOutcome::Error {
|
||||
code: ErrorCode::InternalError,
|
||||
error: e.to_string(),
|
||||
hint: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match handler.save_document(&doc_id, &out_path) {
|
||||
Ok(()) => ToolOutcome::Ok {
|
||||
message: Some(format!(
|
||||
"Generated document from template '{}' with {} replacements at {}",
|
||||
template_name, replace_count, output_path
|
||||
)),
|
||||
},
|
||||
Err(e) => ToolOutcome::Error {
|
||||
code: ErrorCode::InternalError,
|
||||
error: e.to_string(),
|
||||
hint: None,
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
// Optionally close template document
|
||||
let _ = handler.close_document(&doc_id);
|
||||
drop(handler);
|
||||
result
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_ => {
|
||||
ToolOutcome::Error { code: ErrorCode::UnknownTool, error: format!("Unknown or unsupported tool: {}", name), hint: None }
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
})
|
||||
}
|
||||
+37
-8
@@ -15,6 +15,8 @@ mod converter;
|
||||
mod pure_converter;
|
||||
#[cfg(all(feature = "runtime-server", feature = "advanced-docx"))]
|
||||
mod advanced_docx;
|
||||
#[cfg(feature = "http-server")]
|
||||
mod http_server;
|
||||
mod security;
|
||||
|
||||
#[cfg(feature = "embedded-fonts")]
|
||||
@@ -53,6 +55,39 @@ async fn main() -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
// Check if HTTP mode is enabled before consuming args
|
||||
let http_mode = args.http_mode;
|
||||
let http_address = args.http_address.clone();
|
||||
let templates_dir = args.templates_dir.clone();
|
||||
|
||||
// Create the tools provider
|
||||
let security_config = security::SecurityConfig::from_args(args);
|
||||
info!("Starting DOCX MCP Server - Security: {}", security_config.get_summary());
|
||||
info!("Templates directory: {}", templates_dir);
|
||||
|
||||
let provider = DocxToolsProvider::new_with_security_and_templates(
|
||||
security_config,
|
||||
std::path::PathBuf::from(&templates_dir),
|
||||
);
|
||||
|
||||
// Check if HTTP mode is enabled
|
||||
if http_mode {
|
||||
#[cfg(feature = "http-server")]
|
||||
{
|
||||
let addr = http_address.unwrap_or_else(|| "0.0.0.0:3000".to_string());
|
||||
info!("Starting in HTTP mode on {}", addr);
|
||||
return http_server::start_http_server(&addr, provider).await;
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "http-server"))]
|
||||
{
|
||||
eprintln!("HTTP mode requires the 'http-server' feature to be enabled during build.");
|
||||
eprintln!("Rebuild with: cargo build --release --features http-server");
|
||||
return Err(anyhow::anyhow!("HTTP mode not available"));
|
||||
}
|
||||
}
|
||||
|
||||
// Default: stdio mode
|
||||
#[cfg(feature = "runtime-server")]
|
||||
{
|
||||
use mcp_server::{Router, Server};
|
||||
@@ -67,9 +102,6 @@ async fn main() -> Result<()> {
|
||||
use std::future::Future;
|
||||
use tokio::io::{stdin, stdout};
|
||||
|
||||
let security_config = security::SecurityConfig::from_args(args);
|
||||
info!("Starting DOCX MCP Server - Security: {}", security_config.get_summary());
|
||||
|
||||
#[derive(Clone)]
|
||||
struct DocxRouter(docx_tools::DocxToolsProvider);
|
||||
|
||||
@@ -80,7 +112,6 @@ async fn main() -> Result<()> {
|
||||
CapabilitiesBuilder::new().with_tools(true).build()
|
||||
}
|
||||
fn list_tools(&self) -> Vec<SpecTool> {
|
||||
// DocxToolsProvider::list_tools is async; block briefly with tokio runtime handle
|
||||
let rt = tokio::runtime::Handle::current();
|
||||
let tools = rt.block_on(self.0.list_tools());
|
||||
tools.into_iter().map(|t| SpecTool{ name: t.name, description: t.description.unwrap_or_default(), input_schema: t.input_schema }).collect()
|
||||
@@ -90,7 +121,6 @@ async fn main() -> Result<()> {
|
||||
let name = tool_name.to_string();
|
||||
Box::pin(async move {
|
||||
let resp = provider.call_tool(&name, arguments).await;
|
||||
// Convert our CallToolResponse (text JSON) to Content::text
|
||||
let text = match resp.content.get(0) {
|
||||
Some(mcp_core::types::ToolResponseContent::Text(t)) => t.text.clone(),
|
||||
_ => serde_json::to_string(&resp).unwrap_or_else(|_| "{}".to_string()),
|
||||
@@ -108,7 +138,7 @@ async fn main() -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
let router = DocxRouter(DocxToolsProvider::new_with_security(security_config));
|
||||
let router = DocxRouter(provider);
|
||||
let service = RouterService(router);
|
||||
let server = Server::new(service);
|
||||
let transport = mcp_server::ByteTransport::new(stdin(), stdout());
|
||||
@@ -117,9 +147,8 @@ async fn main() -> Result<()> {
|
||||
|
||||
#[cfg(not(feature = "runtime-server"))]
|
||||
{
|
||||
// No runtime server compiled in; if no subcommand was used, exit with guidance
|
||||
eprintln!("Runtime server disabled. Rebuild with --features runtime-server to run the MCP server.");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,18 @@ pub struct Args {
|
||||
#[arg(long, env = "DOCX_MCP_MAX_DOCS")]
|
||||
pub max_docs: Option<usize>,
|
||||
|
||||
/// Enable HTTP server mode for HTML interface
|
||||
#[arg(long, env = "DOCX_MCP_HTTP")]
|
||||
pub http_mode: bool,
|
||||
|
||||
/// HTTP server address and port (default: 0.0.0.0:3000)
|
||||
#[arg(long, env = "DOCX_MCP_HTTP_ADDRESS")]
|
||||
pub http_address: Option<String>,
|
||||
|
||||
/// Path to directory containing template .docx files
|
||||
#[arg(long, env = "DOCX_MCP_TEMPLATES_DIR", default_value = "/templates")]
|
||||
pub templates_dir: String,
|
||||
|
||||
/// Optional top-level subcommand (e.g., fonts download)
|
||||
#[command(subcommand)]
|
||||
pub command: Option<CliCommand>,
|
||||
|
||||
Reference in New Issue
Block a user