Skip to content

Commit 89001a1

Browse files
Add MCP server
1 parent 4309a9b commit 89001a1

File tree

5 files changed

+137
-2
lines changed

5 files changed

+137
-2
lines changed

application/apps/indexer/mcp/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ version = "0.1.0"
44
edition = "2024"
55

66
[dependencies]
7-
rmcp = { version = "0.3", features = ["server", "transport-io"] }
7+
rmcp = { version = "0.8", features = ["server", "transport-io"] }
88
serde = { workspace = true, features = ["derive"] }
99
serde_json.workspace = true
1010
schemars = "1.0"
1111
dirs.workspace = true
1212
anyhow.workspace = true
13+
log.workspace = true
1314

1415
[dev-dependencies]
1516
tempfile.workspace = true
Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
use rmcp::handler::server::router::tool::ToolRouter;
1+
use anyhow::Result;
2+
use rmcp::{
3+
handler::server::{ServerHandler, router::tool::ToolRouter},
4+
model::{ServerCapabilities, ServerInfo},
5+
tool_handler,
6+
};
27

38
use super::ai_config::AiConfig;
49

@@ -7,3 +12,20 @@ pub struct ChipmunkAI {
712
pub config: AiConfig,
813
pub tool_router: ToolRouter<Self>,
914
}
15+
16+
#[tool_handler]
17+
impl ServerHandler for ChipmunkAI {
18+
fn get_info(&self) -> ServerInfo {
19+
ServerInfo {
20+
instructions: Some(
21+
"Sample tool for generating the Search filters for Chipmunk".to_string(),
22+
),
23+
capabilities: ServerCapabilities::builder()
24+
.enable_tools()
25+
.enable_prompts()
26+
.enable_resources()
27+
.build(),
28+
..Default::default()
29+
}
30+
}
31+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
11
mod ai_config;
22
mod ai_manager;
3+
mod parameters;
4+
mod tools;
35
mod utils;
6+
7+
use anyhow::Result;
8+
9+
use ai_config::AiConfig;
10+
use ai_manager::ChipmunkAI;
11+
12+
async fn start() -> Result<()> {
13+
let config = AiConfig::init();
14+
let service = ChipmunkAI::new(config);
15+
Ok(())
16+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
use schemars::JsonSchema;
2+
use serde::{Deserialize, Serialize};
3+
4+
#[derive(Debug, Default, JsonSchema, Serialize, Deserialize)]
5+
pub struct SearchFilter {
6+
pub value: String,
7+
pub is_regex: bool,
8+
pub ignore_case: bool,
9+
pub is_word: bool,
10+
}
11+
12+
#[derive(Debug, Default, JsonSchema, Serialize, Deserialize)]
13+
pub struct FilterParameter {
14+
pub filters: Vec<SearchFilter>,
15+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
use log::info;
2+
use rmcp::{
3+
ErrorData as McpError,
4+
handler::server::wrapper::Parameters,
5+
model::{CallToolResult, Content},
6+
tool, tool_router,
7+
};
8+
9+
use super::{
10+
ai_config::AiConfig,
11+
ai_manager::ChipmunkAI,
12+
parameters::{FilterParameter, SearchFilter},
13+
};
14+
15+
#[tool_router]
16+
impl ChipmunkAI {
17+
#[allow(dead_code)]
18+
pub fn new(config: AiConfig) -> Self {
19+
Self {
20+
config,
21+
tool_router: Self::tool_router(),
22+
}
23+
}
24+
25+
#[tool(description = r#"Generate SearchFilter objects for filtering logs.
26+
27+
This tool accepts one or more filter specifications and returns a list of SearchFilter objects.
28+
Each filter can be customized with flags for regex matching, case sensitivity, and word boundaries.
29+
30+
**Input Parameters:**
31+
- `filters`: An array of filter objects, where each object contains:
32+
- `filter` (string): The text or pattern to search for
33+
- `is_regex` (boolean): true if the filter is a regular expression pattern
34+
- `ignore_case` (boolean): true for case-insensitive matching
35+
- `is_word` (boolean): true to match whole words only (word boundary matching)
36+
37+
**Usage Examples:**
38+
39+
Single filter:
40+
- Input: [{"filter": "error", "is_regex": false, "ignore_case": false, "is_word": false}]
41+
- Use case: Find exact matches of "error"
42+
43+
Multiple filters:
44+
- Input: [
45+
{"filter": "ERROR", "is_regex": false, "ignore_case": true, "is_word": false},
46+
{"filter": "\\d{4}-\\d{2}-\\d{2}", "is_regex": true, "ignore_case": false, "is_word": false}
47+
]
48+
- Use case: Find "ERROR" (any case) OR date patterns
49+
50+
Common patterns:
51+
- Case-insensitive word: {"filter": "warning", "is_regex": false, "ignore_case": true, "is_word": true}
52+
- Regex pattern: {"filter": "\\b(error|fail|exception)\\b", "is_regex": true, "ignore_case": false, "is_word": false}
53+
- Exact match: {"filter": "timeout", "is_regex": false, "ignore_case": false, "is_word": false}
54+
55+
**Natural Language Interpretation:**
56+
When the user provides natural language instructions, interpret them as follows:
57+
- "error" → single filter for "error"
58+
- "error or warning" → two filters, one for "error" and one for "warning"
59+
- "case-insensitive ERROR" → set ignore_case: true
60+
- "match the word 'timeout'" → set is_word: true
61+
- "regex pattern \\d+" → set is_regex: true
62+
- "find ERROR, WARNING, and CRITICAL" → three separate filters
63+
"#)]
64+
async fn apply_filters(
65+
&self,
66+
Parameters(param): Parameters<FilterParameter>,
67+
) -> Result<CallToolResult, McpError> {
68+
let filters = param
69+
.filters
70+
.iter()
71+
.map(|f| SearchFilter {
72+
value: f.value.clone(),
73+
is_regex: f.is_regex,
74+
is_word: f.is_word,
75+
ignore_case: f.ignore_case,
76+
})
77+
.collect::<Vec<SearchFilter>>();
78+
info!("Received Filters from the LLM Agent: {filters:?}");
79+
// TODO: Apply filter via channel
80+
Ok(CallToolResult::success(vec![Content::json(
81+
"Applied filters to the logs",
82+
)?]))
83+
}
84+
}

0 commit comments

Comments
 (0)