|
1 | | -use super::McpServer; |
| 1 | +use super::{Filter, McpServer, Tasks, messages::TaskResponse}; |
2 | 2 |
|
3 | | -use rmcp::tool; |
| 3 | +use tokio::sync::mpsc; |
4 | 4 |
|
| 5 | +use rmcp::{ |
| 6 | + ErrorData as RmcpError, |
| 7 | + handler::server::wrapper::Parameters, |
| 8 | + model::{CallToolResult, Content, ErrorCode}, |
| 9 | + tool, tool_router, |
| 10 | +}; |
| 11 | + |
| 12 | +#[tool_router] |
5 | 13 | impl McpServer { |
6 | | - #[tool(description = "test")] |
7 | | - fn apply_search_filter() {} |
| 14 | + pub fn new() -> (Self, mpsc::Receiver<Tasks>) { |
| 15 | + let (task_tx, task_rx) = mpsc::channel::<Tasks>(32); |
| 16 | + |
| 17 | + ( |
| 18 | + Self { |
| 19 | + task_tx, |
| 20 | + tool_router: Self::tool_router(), |
| 21 | + }, |
| 22 | + task_rx, |
| 23 | + ) |
| 24 | + } |
| 25 | + |
| 26 | + #[tool(description = r#"Generate SearchFilter objects for filtering logs. |
| 27 | +
|
| 28 | +This tool accepts one or more filter specifications and returns a list of SearchFilter objects. |
| 29 | +Each filter can be customized with flags for regex matching, case sensitivity, and word boundaries. |
| 30 | +
|
| 31 | +**Input Parameters:** |
| 32 | +- `filters`: An array of filter objects, where each object contains: |
| 33 | + - `filter` (string): The text or pattern to search for |
| 34 | + - `is_regex` (boolean): true if the filter is a regular expression pattern |
| 35 | + - `ignore_case` (boolean): true for case-insensitive matching |
| 36 | + - `is_word` (boolean): true to match whole words only (word boundary matching) |
| 37 | +
|
| 38 | +**Usage Examples:** |
| 39 | +
|
| 40 | +Single filter: |
| 41 | +- Input: [{"filter": "error", "is_regex": false, "ignore_case": false, "is_word": false}] |
| 42 | +- Use case: Find exact matches of "error" |
| 43 | +
|
| 44 | +Multiple filters: |
| 45 | +- Input: [ |
| 46 | + {"filter": "ERROR", "is_regex": false, "ignore_case": true, "is_word": false}, |
| 47 | + {"filter": "\\d{4}-\\d{2}-\\d{2}", "is_regex": true, "ignore_case": false, "is_word": false} |
| 48 | + ] |
| 49 | +- Use case: Find "ERROR" (any case) OR date patterns |
| 50 | +
|
| 51 | +Common patterns: |
| 52 | +- Case-insensitive word: {"filter": "warning", "is_regex": false, "ignore_case": true, "is_word": true} |
| 53 | +- Regex pattern: {"filter": "\\b(error|fail|exception)\\b", "is_regex": true, "ignore_case": false, "is_word": false} |
| 54 | +- Exact match: {"filter": "timeout", "is_regex": false, "ignore_case": false, "is_word": false} |
| 55 | +
|
| 56 | +**Natural Language Interpretation:** |
| 57 | +When the user provides natural language instructions, interpret them as follows: |
| 58 | +- "error" → single filter for "error" |
| 59 | +- "error or warning" → two filters, one for "error" and one for "warning" |
| 60 | +- "case-insensitive ERROR" → set ignore_case: true |
| 61 | +- "match the word 'timeout'" → set is_word: true |
| 62 | +- "regex pattern \\d+" → set is_regex: true |
| 63 | +- "find ERROR, WARNING, and CRITICAL" → three separate filters |
| 64 | +"#)] |
| 65 | + async fn apply_search_filter( |
| 66 | + &self, |
| 67 | + Parameters(params): Parameters<Vec<Filter>>, |
| 68 | + ) -> Result<CallToolResult, RmcpError> { |
| 69 | + log::info!( |
| 70 | + "Received apply_search_filter tool call with params: {:?}", |
| 71 | + params |
| 72 | + ); |
| 73 | + let (callback_tx, callback_rx) = tokio::sync::oneshot::channel::<TaskResponse>(); |
| 74 | + let task = Tasks::Search { |
| 75 | + filters: vec![], |
| 76 | + callback_tx, |
| 77 | + }; |
| 78 | + let task_tx_clone = self.task_tx.clone(); |
| 79 | + match tokio::spawn(async move { task_tx_clone.send(task).await }).await { |
| 80 | + Ok(_) => log::info!("Sent Search task to MCP server"), |
| 81 | + Err(err) => log::error!( |
| 82 | + "Failed to send Search task to MCP server: ApplyFilter: {}", |
| 83 | + err |
| 84 | + ), |
| 85 | + }; |
| 86 | + |
| 87 | + callback_rx |
| 88 | + .await |
| 89 | + .map(|task_response| { |
| 90 | + //Ok(CallToolResult::success(vec![Content::json("Success")?])) |
| 91 | + match task_response { |
| 92 | + TaskResponse::Success(message) => { |
| 93 | + Ok(CallToolResult::success(vec![Content::json(message)?])) |
| 94 | + } |
| 95 | + TaskResponse::Error(err) => { |
| 96 | + Ok(CallToolResult::error(vec![Content::json(err)?])) |
| 97 | + } |
| 98 | + } |
| 99 | + }) |
| 100 | + .map_err(|err| { |
| 101 | + RmcpError::new( |
| 102 | + ErrorCode::INTERNAL_ERROR, |
| 103 | + format!("Did not receive the response from search filter task {err:?}"), |
| 104 | + None, |
| 105 | + ) |
| 106 | + })? |
| 107 | + } |
8 | 108 | } |
0 commit comments