Implement manual cli tools option
This commit is contained in:
parent
85b4116262
commit
ebede724ad
4 changed files with 428 additions and 18 deletions
|
@ -1,16 +1,19 @@
|
|||
use anyhow::Result;
|
||||
use clap::{Parser, Subcommand};
|
||||
use cratedocs_mcp::tools::DocRouter;
|
||||
use mcp_core::Content;
|
||||
use mcp_server::router::RouterService;
|
||||
use mcp_server::{ByteTransport, Server};
|
||||
use mcp_server::{ByteTransport, Router, Server};
|
||||
use serde_json::json;
|
||||
use std::net::SocketAddr;
|
||||
use tokio::io::{stdin, stdout};
|
||||
use tracing_appender::rolling::{RollingFileAppender, Rotation};
|
||||
use tracing_subscriber::{self, EnvFilter, layer::SubscriberExt, util::SubscriberInitExt};
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
#[command(author, version = "0.1.0", about, long_about = None)]
|
||||
#[command(propagate_version = true)]
|
||||
#[command(disable_version_flag = true)]
|
||||
struct Cli {
|
||||
#[command(subcommand)]
|
||||
command: Commands,
|
||||
|
@ -30,6 +33,36 @@ enum Commands {
|
|||
#[arg(short, long, default_value = "127.0.0.1:8080")]
|
||||
address: String,
|
||||
|
||||
/// Enable debug logging
|
||||
#[arg(short, long)]
|
||||
debug: bool,
|
||||
},
|
||||
/// Test tools directly from the CLI
|
||||
Test {
|
||||
/// The tool to test (lookup_crate, search_crates, lookup_item)
|
||||
#[arg(long, default_value = "lookup_crate")]
|
||||
tool: String,
|
||||
|
||||
/// Crate name for lookup_crate and lookup_item
|
||||
#[arg(long)]
|
||||
crate_name: Option<String>,
|
||||
|
||||
/// Item path for lookup_item (e.g., std::vec::Vec)
|
||||
#[arg(long)]
|
||||
item_path: Option<String>,
|
||||
|
||||
/// Search query for search_crates
|
||||
#[arg(long)]
|
||||
query: Option<String>,
|
||||
|
||||
/// Crate version (optional)
|
||||
#[arg(long)]
|
||||
version: Option<String>,
|
||||
|
||||
/// Result limit for search_crates
|
||||
#[arg(long)]
|
||||
limit: Option<u32>,
|
||||
|
||||
/// Enable debug logging
|
||||
#[arg(short, long)]
|
||||
debug: bool,
|
||||
|
@ -43,6 +76,15 @@ async fn main() -> Result<()> {
|
|||
match cli.command {
|
||||
Commands::Stdio { debug } => run_stdio_server(debug).await,
|
||||
Commands::Http { address, debug } => run_http_server(address, debug).await,
|
||||
Commands::Test {
|
||||
tool,
|
||||
crate_name,
|
||||
item_path,
|
||||
query,
|
||||
version,
|
||||
limit,
|
||||
debug
|
||||
} => run_test_tool(tool, crate_name, item_path, query, version, limit, debug).await,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -98,5 +140,113 @@ async fn run_http_server(address: String, debug: bool) -> Result<()> {
|
|||
let app = cratedocs_mcp::transport::http_sse_server::App::new();
|
||||
axum::serve(listener, app.router()).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Run a direct test of a documentation tool from the CLI
|
||||
async fn run_test_tool(
|
||||
tool: String,
|
||||
crate_name: Option<String>,
|
||||
item_path: Option<String>,
|
||||
query: Option<String>,
|
||||
version: Option<String>,
|
||||
limit: Option<u32>,
|
||||
debug: bool,
|
||||
) -> Result<()> {
|
||||
// Print help information if the tool is "help"
|
||||
if tool == "help" {
|
||||
println!("CrateDocs CLI Tool Tester\n");
|
||||
println!("Usage examples:");
|
||||
println!(" cargo run --bin cratedocs -- test --tool lookup_crate --crate-name serde");
|
||||
println!(" cargo run --bin cratedocs -- test --tool lookup_crate --crate-name tokio --version 1.35.0");
|
||||
println!(" cargo run --bin cratedocs -- test --tool search_crates --query logger\n");
|
||||
println!("Available tools:");
|
||||
println!(" lookup_crate - Look up documentation for a Rust crate");
|
||||
println!(" lookup_item - Look up documentation for a specific item in a crate");
|
||||
println!(" search_crates - Search for crates on crates.io");
|
||||
println!(" help - Show this help information\n");
|
||||
return Ok(());
|
||||
}
|
||||
// Set up console logging
|
||||
let level = if debug { tracing::Level::DEBUG } else { tracing::Level::INFO };
|
||||
|
||||
tracing_subscriber::fmt()
|
||||
.with_max_level(level)
|
||||
.without_time()
|
||||
.with_target(false)
|
||||
.init();
|
||||
|
||||
// Create router instance
|
||||
let router = DocRouter::new();
|
||||
|
||||
tracing::info!("Testing tool: {}", tool);
|
||||
|
||||
// Prepare arguments based on the tool being tested
|
||||
let arguments = match tool.as_str() {
|
||||
"lookup_crate" => {
|
||||
let crate_name = crate_name.ok_or_else(||
|
||||
anyhow::anyhow!("--crate-name is required for lookup_crate tool"))?;
|
||||
|
||||
json!({
|
||||
"crate_name": crate_name,
|
||||
"version": version,
|
||||
})
|
||||
},
|
||||
"lookup_item" => {
|
||||
let crate_name = crate_name.ok_or_else(||
|
||||
anyhow::anyhow!("--crate-name is required for lookup_item tool"))?;
|
||||
let item_path = item_path.ok_or_else(||
|
||||
anyhow::anyhow!("--item-path is required for lookup_item tool"))?;
|
||||
|
||||
json!({
|
||||
"crate_name": crate_name,
|
||||
"item_path": item_path,
|
||||
"version": version,
|
||||
})
|
||||
},
|
||||
"search_crates" => {
|
||||
let query = query.ok_or_else(||
|
||||
anyhow::anyhow!("--query is required for search_crates tool"))?;
|
||||
|
||||
json!({
|
||||
"query": query,
|
||||
"limit": limit,
|
||||
})
|
||||
},
|
||||
_ => return Err(anyhow::anyhow!("Unknown tool: {}", tool)),
|
||||
};
|
||||
|
||||
// Call the tool and get results
|
||||
tracing::debug!("Calling {} with arguments: {}", tool, arguments);
|
||||
println!("Executing {} tool...", tool);
|
||||
|
||||
let result = match router.call_tool(&tool, arguments).await {
|
||||
Ok(result) => result,
|
||||
Err(e) => {
|
||||
eprintln!("\nERROR: {}", e);
|
||||
eprintln!("\nTip: The direct item lookup may require very specific path formats. Try these commands instead:");
|
||||
eprintln!(" - For crate docs: cargo run --bin cratedocs -- test --tool lookup_crate --crate-name tokio");
|
||||
eprintln!(" - For crate docs with version: cargo run --bin cratedocs -- test --tool lookup_crate --crate-name serde --version 1.0.147");
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
// Print results
|
||||
if !result.is_empty() {
|
||||
for content in result {
|
||||
match content {
|
||||
Content::Text(text) => {
|
||||
println!("\n--- TOOL RESULT ---\n");
|
||||
// Access the raw string from TextContent.text field
|
||||
println!("{}", text.text);
|
||||
println!("\n--- END RESULT ---");
|
||||
},
|
||||
_ => println!("Received non-text content"),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println!("Tool returned no results");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -10,6 +10,7 @@ use mcp_server::router::CapabilitiesBuilder;
|
|||
use reqwest::Client;
|
||||
use serde_json::{json, Value};
|
||||
use tokio::sync::Mutex;
|
||||
use html2md::parse_html;
|
||||
|
||||
// Cache for documentation lookups to avoid repeated requests
|
||||
#[derive(Clone)]
|
||||
|
@ -93,14 +94,17 @@ impl DocRouter {
|
|||
)));
|
||||
}
|
||||
|
||||
let body = response.text().await.map_err(|e| {
|
||||
let html_body = response.text().await.map_err(|e| {
|
||||
ToolError::ExecutionError(format!("Failed to read response body: {}", e))
|
||||
})?;
|
||||
|
||||
// Cache the result
|
||||
self.cache.set(cache_key, body.clone()).await;
|
||||
|
||||
Ok(body)
|
||||
// Convert HTML to markdown
|
||||
let markdown_body = parse_html(&html_body);
|
||||
|
||||
// Cache the markdown result
|
||||
self.cache.set(cache_key, markdown_body.clone()).await;
|
||||
|
||||
Ok(markdown_body)
|
||||
}
|
||||
|
||||
// Search crates.io for crates matching a query
|
||||
|
@ -124,7 +128,14 @@ impl DocRouter {
|
|||
ToolError::ExecutionError(format!("Failed to read response body: {}", e))
|
||||
})?;
|
||||
|
||||
Ok(body)
|
||||
// Check if response is JSON (API response) or HTML (web page)
|
||||
if body.trim().starts_with('{') {
|
||||
// This is likely JSON data, return as is
|
||||
Ok(body)
|
||||
} else {
|
||||
// This is likely HTML, convert to markdown
|
||||
Ok(parse_html(&body))
|
||||
}
|
||||
}
|
||||
|
||||
// Get documentation for a specific item in a crate
|
||||
|
@ -159,14 +170,17 @@ impl DocRouter {
|
|||
)));
|
||||
}
|
||||
|
||||
let body = response.text().await.map_err(|e| {
|
||||
let html_body = response.text().await.map_err(|e| {
|
||||
ToolError::ExecutionError(format!("Failed to read response body: {}", e))
|
||||
})?;
|
||||
|
||||
// Cache the result
|
||||
self.cache.set(cache_key, body.clone()).await;
|
||||
|
||||
Ok(body)
|
||||
// Convert HTML to markdown
|
||||
let markdown_body = parse_html(&html_body);
|
||||
|
||||
// Cache the markdown result
|
||||
self.cache.set(cache_key, markdown_body.clone()).await;
|
||||
|
||||
Ok(markdown_body)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -176,10 +190,11 @@ impl mcp_server::Router for DocRouter {
|
|||
}
|
||||
|
||||
fn instructions(&self) -> String {
|
||||
"This server provides tools for looking up Rust crate documentation. \
|
||||
"This server provides tools for looking up Rust crate documentation in markdown format. \
|
||||
You can search for crates, lookup documentation for specific crates or \
|
||||
items within crates. Use these tools to find information about Rust libraries \
|
||||
you are not familiar with.".to_string()
|
||||
you are not familiar with. All HTML documentation is automatically converted to markdown \
|
||||
for better compatibility with language models.".to_string()
|
||||
}
|
||||
|
||||
fn capabilities(&self) -> ServerCapabilities {
|
||||
|
@ -194,7 +209,7 @@ impl mcp_server::Router for DocRouter {
|
|||
vec![
|
||||
Tool::new(
|
||||
"lookup_crate".to_string(),
|
||||
"Look up documentation for a Rust crate".to_string(),
|
||||
"Look up documentation for a Rust crate (returns markdown)".to_string(),
|
||||
json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -212,7 +227,7 @@ impl mcp_server::Router for DocRouter {
|
|||
),
|
||||
Tool::new(
|
||||
"search_crates".to_string(),
|
||||
"Search for Rust crates on crates.io".to_string(),
|
||||
"Search for Rust crates on crates.io (returns JSON or markdown)".to_string(),
|
||||
json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -230,7 +245,7 @@ impl mcp_server::Router for DocRouter {
|
|||
),
|
||||
Tool::new(
|
||||
"lookup_item".to_string(),
|
||||
"Look up documentation for a specific item in a Rust crate".to_string(),
|
||||
"Look up documentation for a specific item in a Rust crate (returns markdown)".to_string(),
|
||||
json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue