feat(theme): add tool_output color to themes
- Added a `tool_output` color to the `Theme` struct. - Updated all built-in themes to include the new color. - Modified the TUI to use the `tool_output` color for rendering tool output.
This commit is contained in:
111
crates/owlen-core/src/tools/fs_tools.rs
Normal file
111
crates/owlen-core/src/tools/fs_tools.rs
Normal file
@@ -0,0 +1,111 @@
|
||||
use crate::tools::{Tool, ToolResult};
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use serde::Deserialize;
|
||||
use serde_json::json;
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use path_clean::PathClean;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct FileArgs {
|
||||
path: String,
|
||||
}
|
||||
|
||||
fn sanitize_path(path: &str, root: &Path) -> Result<PathBuf> {
|
||||
let path = Path::new(path);
|
||||
let path = if path.is_absolute() {
|
||||
path.strip_prefix("/")
|
||||
.map_err(|_| anyhow::anyhow!("Invalid path"))?
|
||||
.to_path_buf()
|
||||
} else {
|
||||
path.to_path_buf()
|
||||
};
|
||||
|
||||
let full_path = root.join(path).clean();
|
||||
|
||||
if !full_path.starts_with(root) {
|
||||
return Err(anyhow::anyhow!("Path traversal detected"));
|
||||
}
|
||||
|
||||
Ok(full_path)
|
||||
}
|
||||
|
||||
pub struct ResourcesListTool;
|
||||
|
||||
#[async_trait]
|
||||
impl Tool for ResourcesListTool {
|
||||
fn name(&self) -> &'static str {
|
||||
"resources/list"
|
||||
}
|
||||
|
||||
fn description(&self) -> &'static str {
|
||||
"Lists directory contents."
|
||||
}
|
||||
|
||||
fn schema(&self) -> serde_json::Value {
|
||||
json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"path": {
|
||||
"type": "string",
|
||||
"description": "The path to the directory to list."
|
||||
}
|
||||
},
|
||||
"required": ["path"]
|
||||
})
|
||||
}
|
||||
|
||||
async fn execute(&self, args: serde_json::Value) -> Result<ToolResult> {
|
||||
let args: FileArgs = serde_json::from_value(args)?;
|
||||
let root = env::current_dir()?;
|
||||
let full_path = sanitize_path(&args.path, &root)?;
|
||||
|
||||
let entries = fs::read_dir(full_path)?;
|
||||
|
||||
let mut result = Vec::new();
|
||||
for entry in entries {
|
||||
let entry = entry?;
|
||||
result.push(entry.file_name().to_string_lossy().to_string());
|
||||
}
|
||||
|
||||
Ok(ToolResult::success(serde_json::to_value(result)?))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ResourcesGetTool;
|
||||
|
||||
#[async_trait]
|
||||
impl Tool for ResourcesGetTool {
|
||||
fn name(&self) -> &'static str {
|
||||
"resources/get"
|
||||
}
|
||||
|
||||
fn description(&self) -> &'static str {
|
||||
"Reads file content."
|
||||
}
|
||||
|
||||
fn schema(&self) -> serde_json::Value {
|
||||
json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"path": {
|
||||
"type": "string",
|
||||
"description": "The path to the file to read."
|
||||
}
|
||||
},
|
||||
"required": ["path"]
|
||||
})
|
||||
}
|
||||
|
||||
async fn execute(&self, args: serde_json::Value) -> Result<ToolResult> {
|
||||
let args: FileArgs = serde_json::from_value(args)?;
|
||||
let root = env::current_dir()?;
|
||||
let full_path = sanitize_path(&args.path, &root)?;
|
||||
|
||||
let content = fs::read_to_string(full_path)?;
|
||||
|
||||
Ok(ToolResult::success(serde_json::to_value(content)?))
|
||||
}
|
||||
}
|
||||
11
crates/owlen-mcp-server/Cargo.toml
Normal file
11
crates/owlen-mcp-server/Cargo.toml
Normal file
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "owlen-mcp-server"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1.0", features = ["full"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
anyhow = "1.0"
|
||||
path-clean = "1.0"
|
||||
175
crates/owlen-mcp-server/src/main.rs
Normal file
175
crates/owlen-mcp-server/src/main.rs
Normal file
@@ -0,0 +1,175 @@
|
||||
use path_clean::PathClean;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use tokio::io::{self, AsyncBufReadExt, AsyncWriteExt};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Request {
|
||||
id: u64,
|
||||
method: String,
|
||||
params: serde_json::Value,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct Response {
|
||||
id: u64,
|
||||
result: serde_json::Value,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct ErrorResponse {
|
||||
id: u64,
|
||||
error: JsonRpcError,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct JsonRpcError {
|
||||
code: i64,
|
||||
message: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct FileArgs {
|
||||
path: String,
|
||||
}
|
||||
|
||||
async fn handle_request(req: Request, root: &Path) -> Result<serde_json::Value, JsonRpcError> {
|
||||
match req.method.as_str() {
|
||||
"resources/list" => {
|
||||
let args: FileArgs = serde_json::from_value(req.params).map_err(|e| JsonRpcError {
|
||||
code: -32602,
|
||||
message: format!("Invalid params: {}", e),
|
||||
})?;
|
||||
resources_list(&args.path, root).await
|
||||
}
|
||||
"resources/get" => {
|
||||
let args: FileArgs = serde_json::from_value(req.params).map_err(|e| JsonRpcError {
|
||||
code: -32602,
|
||||
message: format!("Invalid params: {}", e),
|
||||
})?;
|
||||
resources_get(&args.path, root).await
|
||||
}
|
||||
_ => Err(JsonRpcError {
|
||||
code: -32601,
|
||||
message: "Method not found".to_string(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn sanitize_path(path: &str, root: &Path) -> Result<PathBuf, JsonRpcError> {
|
||||
let path = Path::new(path);
|
||||
let path = if path.is_absolute() {
|
||||
path.strip_prefix("/")
|
||||
.map_err(|_| JsonRpcError {
|
||||
code: -32602,
|
||||
message: "Invalid path".to_string(),
|
||||
})?
|
||||
.to_path_buf()
|
||||
} else {
|
||||
path.to_path_buf()
|
||||
};
|
||||
|
||||
let full_path = root.join(path).clean();
|
||||
|
||||
if !full_path.starts_with(root) {
|
||||
return Err(JsonRpcError {
|
||||
code: -32602,
|
||||
message: "Path traversal detected".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(full_path)
|
||||
}
|
||||
|
||||
async fn resources_list(path: &str, root: &Path) -> Result<serde_json::Value, JsonRpcError> {
|
||||
let full_path = sanitize_path(path, root)?;
|
||||
|
||||
let entries = fs::read_dir(full_path)
|
||||
.map_err(|e| JsonRpcError {
|
||||
code: -32000,
|
||||
message: format!("Failed to read directory: {}", e),
|
||||
})?;
|
||||
|
||||
let mut result = Vec::new();
|
||||
for entry in entries {
|
||||
let entry = entry.map_err(|e| JsonRpcError {
|
||||
code: -32000,
|
||||
message: format!("Failed to read directory entry: {}", e),
|
||||
})?;
|
||||
result.push(entry.file_name().to_string_lossy().to_string());
|
||||
}
|
||||
|
||||
Ok(serde_json::json!(result))
|
||||
}
|
||||
|
||||
async fn resources_get(path: &str, root: &Path) -> Result<serde_json::Value, JsonRpcError> {
|
||||
let full_path = sanitize_path(path, root)?;
|
||||
|
||||
let content = fs::read_to_string(full_path).map_err(|e| JsonRpcError {
|
||||
code: -32000,
|
||||
message: format!("Failed to read file: {}", e),
|
||||
})?;
|
||||
|
||||
Ok(serde_json::json!(content))
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
let root = env::current_dir()?;
|
||||
let mut stdin = io::BufReader::new(io::stdin());
|
||||
let mut stdout = io::stdout();
|
||||
|
||||
loop {
|
||||
let mut line = String::new();
|
||||
match stdin.read_line(&mut line).await {
|
||||
Ok(0) => {
|
||||
// EOF
|
||||
break;
|
||||
}
|
||||
Ok(_) => {
|
||||
let req: Request = match serde_json::from_str(&line) {
|
||||
Ok(req) => req,
|
||||
Err(e) => {
|
||||
let err_resp = ErrorResponse {
|
||||
id: 0,
|
||||
error: JsonRpcError {
|
||||
code: -32700,
|
||||
message: format!("Parse error: {}", e),
|
||||
},
|
||||
};
|
||||
let resp_str = serde_json::to_string(&err_resp)?;
|
||||
stdout.write_all(resp_str.as_bytes()).await?;
|
||||
stdout.write_all(b"\n").await?;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let request_id = req.id;
|
||||
|
||||
match handle_request(req, &root).await {
|
||||
Ok(result) => {
|
||||
let resp = Response { id: request_id, result };
|
||||
let resp_str = serde_json::to_string(&resp)?;
|
||||
stdout.write_all(resp_str.as_bytes()).await?;
|
||||
stdout.write_all(b"\n").await?;
|
||||
}
|
||||
Err(error) => {
|
||||
let err_resp = ErrorResponse { id: request_id, error };
|
||||
let resp_str = serde_json::to_string(&err_resp)?;
|
||||
stdout.write_all(resp_str.as_bytes()).await?;
|
||||
stdout.write_all(b"\n").await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
// Handle read error
|
||||
eprintln!("Error reading from stdin: {}", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user