Files
mcp-memory/server/src/handlers/http.rs
T

144 lines
4.2 KiB
Rust

use crate::router::McpTool;
use crate::state::MemoryState;
use crate::tools::FetchUrlTool;
use async_trait::async_trait;
use serde_json::Value;
use std::sync::Arc;
pub struct FetchUrlHandler;
#[async_trait]
impl McpTool for FetchUrlHandler {
fn name(&self) -> &'static str {
"fetch_url"
}
fn schema(&self) -> Value {
crate::mcp::tool_def::<FetchUrlTool>(
"fetch_url",
"Fetch content from a URL via an HTTP request natively (supports proxy config via environment variables: HTTP_PROXY, HTTPS_PROXY, NO_PROXY)",
)
}
async fn execute(&self, args: Value, _state: Arc<MemoryState>) -> crate::error::Result<String> {
let req: FetchUrlTool = serde_json::from_value(args).map_err(|e| {
crate::error::AppError::BadRequest(format!(
"Schema validation failed. Your JSON arguments do not match the expected tool schema: {}",
e
))
})?;
let method_str = req.method.unwrap_or_else(|| "GET".to_string());
let headers: Vec<String> = req.headers.unwrap_or_default().into_iter()
.map(|(k, v)| format!("{}={}", k, v))
.collect();
// Pass to our agentic-tools-rs library directly
let res = agentic_tools_rs::commands::llmfetch::run(
&req.url,
&method_str,
req.body.as_ref(),
&headers,
true, // Always condense for MCP
).await.map_err(|e| {
crate::error::AppError::Internal(format!("Agentic fetch failed: {}", e))
})?;
Ok(res)
}
}
#[cfg(test)]
mod tests {
use super::*;
use axum::{routing::{get, post}, Router};
use tokio::net::TcpListener;
async fn spawn_test_server() -> String {
let app = Router::new()
.route("/test", get(|| async { "Hello, World!" }))
.route(
"/not_found",
get(|| async {
(
axum::http::StatusCode::NOT_FOUND,
"Not Found Error",
)
}),
)
.route("/echo", post(|body: String| async move { format!("ECHO: {}", body) }));
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let port = listener.local_addr().unwrap().port();
tokio::spawn(async move {
axum::serve(listener, app).await.unwrap();
});
format!("http://127.0.0.1:{}", port)
}
#[tokio::test]
async fn test_fetch_url_success() {
let base_url = spawn_test_server().await;
let handler = FetchUrlHandler;
let url = format!("{}/test", base_url);
let res = handler
.execute(
serde_json::json!({
"url": url
}),
Arc::new(MemoryState::new_in_memory()),
)
.await
.unwrap();
assert!(res.contains("HTTP Status: 200 OK"));
assert!(res.contains("Hello, World!"));
}
#[tokio::test]
async fn test_fetch_url_not_found() {
let base_url = spawn_test_server().await;
let handler = FetchUrlHandler;
let url = format!("{}/not_found", base_url);
let res = handler
.execute(
serde_json::json!({
"url": url
}),
Arc::new(MemoryState::new_in_memory()),
)
.await
.unwrap();
assert!(res.contains("HTTP Status: 404 Not Found"));
assert!(res.contains("Not Found Error"));
}
#[tokio::test]
async fn test_fetch_url_post() {
let base_url = spawn_test_server().await;
let handler = FetchUrlHandler;
let url = format!("{}/echo", base_url);
let res = handler
.execute(
serde_json::json!({
"url": url,
"method": "POST",
"body": "test_body_content"
}),
Arc::new(MemoryState::new_in_memory()),
)
.await
.unwrap();
assert!(res.contains("HTTP Status: 200 OK"));
assert!(res.contains("ECHO: test_body_content"));
}
}