Skip to content

Commit 24066ef

Browse files
committed
feat(clp-mcp-server): Add query validation to KQL tool calls
1 parent f798f1f commit 24066ef

File tree

12 files changed

+1568
-7
lines changed

12 files changed

+1568
-7
lines changed

components/clp-mcp-server/clp_mcp_server/server/server.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@
88
from starlette.responses import PlainTextResponse
99

1010
from clp_mcp_server.clp_connector import ClpConnector
11+
from clp_mcp_server.validator import validate_query
1112

1213
from . import constants
1314
from .session_manager import SessionManager
1415
from .utils import format_query_results, parse_timestamp_range, sort_by_timestamp
1516

1617

17-
def create_mcp_server(clp_config: CLPConfig) -> FastMCP:
18+
def create_mcp_server(clp_config: CLPConfig) -> FastMCP: # noqa: C901
1819
"""
1920
Creates and defines API tool calls for the CLP MCP server.
2021
@@ -47,6 +48,10 @@ async def _execute_kql_query(
4748
:return: A dictionary with the following key-value pair on failures:
4849
- "Error": An error message describing the failure.
4950
"""
51+
valid, err_msg = validate_query(kql_query)
52+
if not valid:
53+
return {"Error": f"Error parsing query: {err_msg}"}
54+
5055
try:
5156
query_id = await connector.submit_query(kql_query, begin_ts, end_ts)
5257
await connector.wait_query_completion(query_id)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
"""Initializer for the validator package."""
2+
3+
from .validate import validate_query
4+
5+
__all__ = ["validate_query"]
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
# Generated from /home/huangshi/clp/components/core/src/clp_s/search/kql/Kql.g4 by ANTLR 4.13.2
2+
import sys
3+
4+
from antlr4 import *
5+
6+
if sys.version_info[1] > 5:
7+
from typing import TextIO
8+
else:
9+
from typing.io import TextIO
10+
11+
12+
def serializedATN():
13+
return [
14+
4,0,14,163,6,-1,2,0,7,0,2,1,7,1,2,2,7,2,2,3,7,3,2,4,7,4,2,5,7,5,
15+
2,6,7,6,2,7,7,7,2,8,7,8,2,9,7,9,2,10,7,10,2,11,7,11,2,12,7,12,2,
16+
13,7,13,2,14,7,14,2,15,7,15,2,16,7,16,2,17,7,17,2,18,7,18,2,19,7,
17+
19,2,20,7,20,2,21,7,21,2,22,7,22,1,0,1,0,1,1,1,1,1,2,1,2,1,3,1,3,
18+
1,4,1,4,1,5,1,5,1,5,1,5,1,6,1,6,1,6,1,7,1,7,1,7,1,7,1,8,1,8,1,8,
19+
1,8,1,8,1,8,1,8,1,8,4,8,77,8,8,11,8,12,8,78,1,8,1,8,1,8,4,8,84,8,
20+
8,11,8,12,8,85,3,8,88,8,8,1,8,1,8,1,9,1,9,3,9,94,8,9,1,10,1,10,5,
21+
10,98,8,10,10,10,12,10,101,9,10,1,10,1,10,1,11,4,11,106,8,11,11,
22+
11,12,11,107,1,12,1,12,1,12,1,12,3,12,114,8,12,1,13,1,13,1,13,1,
23+
13,1,13,3,13,121,8,13,1,14,1,14,1,15,1,15,1,15,3,15,128,8,15,1,16,
24+
1,16,1,16,1,16,1,16,3,16,135,8,16,1,17,1,17,1,17,1,18,1,18,1,18,
25+
1,18,1,18,1,18,3,18,146,8,18,1,19,1,19,1,20,1,20,1,20,1,20,1,20,
26+
1,20,1,20,1,20,1,21,1,21,1,22,1,22,1,22,1,22,0,0,23,1,1,3,2,5,3,
27+
7,4,9,5,11,6,13,7,15,8,17,9,19,10,21,11,23,12,25,0,27,0,29,0,31,
28+
0,33,13,35,0,37,0,39,0,41,0,43,0,45,14,1,0,13,2,0,65,65,97,97,2,
29+
0,78,78,110,110,2,0,68,68,100,100,2,0,79,79,111,111,2,0,82,82,114,
30+
114,2,0,84,84,116,116,1,0,34,34,11,0,9,10,13,13,32,32,34,34,40,41,
31+
58,58,60,60,62,62,92,92,123,123,125,125,2,0,42,42,63,63,2,0,60,60,
32+
62,62,9,0,33,36,40,42,46,46,58,58,60,60,62,64,92,92,123,123,125,
33+
125,3,0,48,57,65,70,97,102,3,0,9,10,13,13,32,32,171,0,1,1,0,0,0,
34+
0,3,1,0,0,0,0,5,1,0,0,0,0,7,1,0,0,0,0,9,1,0,0,0,0,11,1,0,0,0,0,13,
35+
1,0,0,0,0,15,1,0,0,0,0,17,1,0,0,0,0,19,1,0,0,0,0,21,1,0,0,0,0,23,
36+
1,0,0,0,0,33,1,0,0,0,0,45,1,0,0,0,1,47,1,0,0,0,3,49,1,0,0,0,5,51,
37+
1,0,0,0,7,53,1,0,0,0,9,55,1,0,0,0,11,57,1,0,0,0,13,61,1,0,0,0,15,
38+
64,1,0,0,0,17,68,1,0,0,0,19,93,1,0,0,0,21,95,1,0,0,0,23,105,1,0,
39+
0,0,25,113,1,0,0,0,27,120,1,0,0,0,29,122,1,0,0,0,31,127,1,0,0,0,
40+
33,134,1,0,0,0,35,136,1,0,0,0,37,145,1,0,0,0,39,147,1,0,0,0,41,149,
41+
1,0,0,0,43,157,1,0,0,0,45,159,1,0,0,0,47,48,5,58,0,0,48,2,1,0,0,
42+
0,49,50,5,123,0,0,50,4,1,0,0,0,51,52,5,125,0,0,52,6,1,0,0,0,53,54,
43+
5,40,0,0,54,8,1,0,0,0,55,56,5,41,0,0,56,10,1,0,0,0,57,58,7,0,0,0,
44+
58,59,7,1,0,0,59,60,7,2,0,0,60,12,1,0,0,0,61,62,7,3,0,0,62,63,7,
45+
4,0,0,63,14,1,0,0,0,64,65,7,1,0,0,65,66,7,3,0,0,66,67,7,5,0,0,67,
46+
16,1,0,0,0,68,69,5,100,0,0,69,70,5,97,0,0,70,71,5,116,0,0,71,72,
47+
5,101,0,0,72,73,5,40,0,0,73,87,1,0,0,0,74,76,5,34,0,0,75,77,3,25,
48+
12,0,76,75,1,0,0,0,77,78,1,0,0,0,78,76,1,0,0,0,78,79,1,0,0,0,79,
49+
80,1,0,0,0,80,81,5,34,0,0,81,88,1,0,0,0,82,84,3,25,12,0,83,82,1,
50+
0,0,0,84,85,1,0,0,0,85,83,1,0,0,0,85,86,1,0,0,0,86,88,1,0,0,0,87,
51+
74,1,0,0,0,87,83,1,0,0,0,88,89,1,0,0,0,89,90,5,41,0,0,90,18,1,0,
52+
0,0,91,94,3,21,10,0,92,94,3,23,11,0,93,91,1,0,0,0,93,92,1,0,0,0,
53+
94,20,1,0,0,0,95,99,5,34,0,0,96,98,3,25,12,0,97,96,1,0,0,0,98,101,
54+
1,0,0,0,99,97,1,0,0,0,99,100,1,0,0,0,100,102,1,0,0,0,101,99,1,0,
55+
0,0,102,103,5,34,0,0,103,22,1,0,0,0,104,106,3,27,13,0,105,104,1,
56+
0,0,0,106,107,1,0,0,0,107,105,1,0,0,0,107,108,1,0,0,0,108,24,1,0,
57+
0,0,109,114,3,37,18,0,110,111,5,92,0,0,111,114,5,34,0,0,112,114,
58+
8,6,0,0,113,109,1,0,0,0,113,110,1,0,0,0,113,112,1,0,0,0,114,26,1,
59+
0,0,0,115,121,3,37,18,0,116,121,3,35,17,0,117,121,3,29,14,0,118,
60+
121,3,41,20,0,119,121,8,7,0,0,120,115,1,0,0,0,120,116,1,0,0,0,120,
61+
117,1,0,0,0,120,118,1,0,0,0,120,119,1,0,0,0,121,28,1,0,0,0,122,123,
62+
7,8,0,0,123,30,1,0,0,0,124,128,3,11,5,0,125,128,3,13,6,0,126,128,
63+
3,15,7,0,127,124,1,0,0,0,127,125,1,0,0,0,127,126,1,0,0,0,128,32,
64+
1,0,0,0,129,130,5,60,0,0,130,135,5,61,0,0,131,132,5,62,0,0,132,135,
65+
5,61,0,0,133,135,7,9,0,0,134,129,1,0,0,0,134,131,1,0,0,0,134,133,
66+
1,0,0,0,135,34,1,0,0,0,136,137,5,92,0,0,137,138,3,39,19,0,138,36,
67+
1,0,0,0,139,140,5,92,0,0,140,146,5,116,0,0,141,142,5,92,0,0,142,
68+
146,5,114,0,0,143,144,5,92,0,0,144,146,5,110,0,0,145,139,1,0,0,0,
69+
145,141,1,0,0,0,145,143,1,0,0,0,146,38,1,0,0,0,147,148,7,10,0,0,
70+
148,40,1,0,0,0,149,150,5,92,0,0,150,151,5,117,0,0,151,152,1,0,0,
71+
0,152,153,3,43,21,0,153,154,3,43,21,0,154,155,3,43,21,0,155,156,
72+
3,43,21,0,156,42,1,0,0,0,157,158,7,11,0,0,158,44,1,0,0,0,159,160,
73+
7,12,0,0,160,161,1,0,0,0,161,162,6,22,0,0,162,46,1,0,0,0,12,0,78,
74+
85,87,93,99,107,113,120,127,134,145,1,6,0,0
75+
]
76+
77+
class KqlLexer(Lexer):
78+
79+
atn = ATNDeserializer().deserialize(serializedATN())
80+
81+
decisionsToDFA = [ DFA(ds, i) for i, ds in enumerate(atn.decisionToState) ]
82+
83+
T__0 = 1
84+
T__1 = 2
85+
T__2 = 3
86+
T__3 = 4
87+
T__4 = 5
88+
AND = 6
89+
OR = 7
90+
NOT = 8
91+
DATE_LITERAL = 9
92+
LITERAL = 10
93+
QUOTED_STRING = 11
94+
UNQUOTED_LITERAL = 12
95+
RANGE_OPERATOR = 13
96+
SPACE = 14
97+
98+
channelNames = [ "DEFAULT_TOKEN_CHANNEL", "HIDDEN" ]
99+
100+
modeNames = [ "DEFAULT_MODE" ]
101+
102+
literalNames = [ "<INVALID>",
103+
"':'", "'{'", "'}'", "'('", "')'" ]
104+
105+
symbolicNames = [ "<INVALID>",
106+
"AND", "OR", "NOT", "DATE_LITERAL", "LITERAL", "QUOTED_STRING",
107+
"UNQUOTED_LITERAL", "RANGE_OPERATOR", "SPACE" ]
108+
109+
ruleNames = [ "T__0", "T__1", "T__2", "T__3", "T__4", "AND", "OR", "NOT",
110+
"DATE_LITERAL", "LITERAL", "QUOTED_STRING", "UNQUOTED_LITERAL",
111+
"QUOTED_CHARACTER", "UNQUOTED_CHARACTER", "WILDCARD",
112+
"KEYWORD", "RANGE_OPERATOR", "ESCAPED_SPECIAL_CHARACTER",
113+
"ESCAPED_SPACE", "SPECIAL_CHARACTER", "UNICODE", "HEXDIGIT",
114+
"SPACE" ]
115+
116+
grammarFileName = "Kql.g4"
117+
118+
def __init__(self, input=None, output:TextIO = sys.stdout):
119+
super().__init__(input, output)
120+
self.checkVersion("4.13.2")
121+
self._interp = LexerATNSimulator(self, self.atn, self.decisionsToDFA, PredictionContextCache())
122+
self._actions = None
123+
self._predicates = None
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
# Generated from /home/huangshi/clp/components/core/src/clp_s/search/kql/Kql.g4 by ANTLR 4.13.2
2+
from antlr4 import *
3+
4+
if "." in __name__:
5+
from .KqlParser import KqlParser
6+
else:
7+
from KqlParser import KqlParser
8+
9+
# This class defines a complete listener for a parse tree produced by KqlParser.
10+
class KqlListener(ParseTreeListener):
11+
12+
# Enter a parse tree produced by KqlParser#start.
13+
def enterStart(self, ctx:KqlParser.StartContext):
14+
pass
15+
16+
# Exit a parse tree produced by KqlParser#start.
17+
def exitStart(self, ctx:KqlParser.StartContext):
18+
pass
19+
20+
21+
# Enter a parse tree produced by KqlParser#Expr.
22+
def enterExpr(self, ctx:KqlParser.ExprContext):
23+
pass
24+
25+
# Exit a parse tree produced by KqlParser#Expr.
26+
def exitExpr(self, ctx:KqlParser.ExprContext):
27+
pass
28+
29+
30+
# Enter a parse tree produced by KqlParser#NestedQuery.
31+
def enterNestedQuery(self, ctx:KqlParser.NestedQueryContext):
32+
pass
33+
34+
# Exit a parse tree produced by KqlParser#NestedQuery.
35+
def exitNestedQuery(self, ctx:KqlParser.NestedQueryContext):
36+
pass
37+
38+
39+
# Enter a parse tree produced by KqlParser#NotQuery.
40+
def enterNotQuery(self, ctx:KqlParser.NotQueryContext):
41+
pass
42+
43+
# Exit a parse tree produced by KqlParser#NotQuery.
44+
def exitNotQuery(self, ctx:KqlParser.NotQueryContext):
45+
pass
46+
47+
48+
# Enter a parse tree produced by KqlParser#SubQuery.
49+
def enterSubQuery(self, ctx:KqlParser.SubQueryContext):
50+
pass
51+
52+
# Exit a parse tree produced by KqlParser#SubQuery.
53+
def exitSubQuery(self, ctx:KqlParser.SubQueryContext):
54+
pass
55+
56+
57+
# Enter a parse tree produced by KqlParser#OrAndQuery.
58+
def enterOrAndQuery(self, ctx:KqlParser.OrAndQueryContext):
59+
pass
60+
61+
# Exit a parse tree produced by KqlParser#OrAndQuery.
62+
def exitOrAndQuery(self, ctx:KqlParser.OrAndQueryContext):
63+
pass
64+
65+
66+
# Enter a parse tree produced by KqlParser#expression.
67+
def enterExpression(self, ctx:KqlParser.ExpressionContext):
68+
pass
69+
70+
# Exit a parse tree produced by KqlParser#expression.
71+
def exitExpression(self, ctx:KqlParser.ExpressionContext):
72+
pass
73+
74+
75+
# Enter a parse tree produced by KqlParser#column_range_expression.
76+
def enterColumn_range_expression(self, ctx:KqlParser.Column_range_expressionContext):
77+
pass
78+
79+
# Exit a parse tree produced by KqlParser#column_range_expression.
80+
def exitColumn_range_expression(self, ctx:KqlParser.Column_range_expressionContext):
81+
pass
82+
83+
84+
# Enter a parse tree produced by KqlParser#column_value_expression.
85+
def enterColumn_value_expression(self, ctx:KqlParser.Column_value_expressionContext):
86+
pass
87+
88+
# Exit a parse tree produced by KqlParser#column_value_expression.
89+
def exitColumn_value_expression(self, ctx:KqlParser.Column_value_expressionContext):
90+
pass
91+
92+
93+
# Enter a parse tree produced by KqlParser#column.
94+
def enterColumn(self, ctx:KqlParser.ColumnContext):
95+
pass
96+
97+
# Exit a parse tree produced by KqlParser#column.
98+
def exitColumn(self, ctx:KqlParser.ColumnContext):
99+
pass
100+
101+
102+
# Enter a parse tree produced by KqlParser#value_expression.
103+
def enterValue_expression(self, ctx:KqlParser.Value_expressionContext):
104+
pass
105+
106+
# Exit a parse tree produced by KqlParser#value_expression.
107+
def exitValue_expression(self, ctx:KqlParser.Value_expressionContext):
108+
pass
109+
110+
111+
# Enter a parse tree produced by KqlParser#list_of_values.
112+
def enterList_of_values(self, ctx:KqlParser.List_of_valuesContext):
113+
pass
114+
115+
# Exit a parse tree produced by KqlParser#list_of_values.
116+
def exitList_of_values(self, ctx:KqlParser.List_of_valuesContext):
117+
pass
118+
119+
120+
121+
del KqlParser

0 commit comments

Comments
 (0)