7
7
from fastmcp .tools import FunctionTool
8
8
from pydantic import BaseModel , Field
9
9
10
- from keboola_mcp_server .client import GlobalSearchResponse , GlobalSearchType , KeboolaClient , SuggestedComponent
10
+ from keboola_mcp_server .client import GlobalSearchResponse , ItemType , KeboolaClient , SuggestedComponent
11
11
from keboola_mcp_server .errors import tool_errors
12
12
from keboola_mcp_server .mcp import with_session_state
13
13
20
20
def add_search_tools (mcp : FastMCP ) -> None :
21
21
"""Add tools to the MCP server."""
22
22
search_tools = [
23
- global_search ,
24
23
find_component_id ,
24
+ find_ids_by_name ,
25
25
]
26
26
for tool in search_tools :
27
27
LOG .info (f'Adding tool { tool .__name__ } to the MCP server.' )
@@ -30,19 +30,19 @@ def add_search_tools(mcp: FastMCP) -> None:
30
30
LOG .info ('Search tools initialized.' )
31
31
32
32
33
- class GlobalSearchItemsGroup (BaseModel ):
33
+ class ItemsGroup (BaseModel ):
34
34
"""Group of items of the same type found in the global search."""
35
35
36
- class GroupItem (BaseModel ):
36
+ class Item (BaseModel ):
37
37
"""An item corresponding to its group type found in the global search."""
38
38
39
39
name : str = Field (description = 'The name of the item.' )
40
40
id : str = Field (description = 'The id of the item.' )
41
- created : datetime = Field (description = 'The date and time the entity was created.' )
41
+ created : datetime = Field (description = 'The date and time the item was created.' )
42
42
additional_info : dict [str , Any ] = Field (description = 'Additional information about the item.' )
43
43
44
44
@classmethod
45
- def from_api_response (cls , item : GlobalSearchResponse .Item ) -> 'GlobalSearchItemsGroup.GroupItem ' :
45
+ def from_api_response (cls , item : GlobalSearchResponse .Item ) -> 'ItemsGroup.Item ' :
46
46
"""Creates an Item from the item API response."""
47
47
add_info = {}
48
48
if item .type == 'table' :
@@ -60,54 +60,51 @@ def from_api_response(cls, item: GlobalSearchResponse.Item) -> 'GlobalSearchItem
60
60
add_info ['configuration_name' ] = configuration_info ['name' ]
61
61
return cls .model_construct (name = item .name , id = item .id , created = item .created , additional_info = add_info )
62
62
63
- group_type : GlobalSearchType = Field (description = 'The type of the items in the group.' )
64
- group_count : int = Field (description = 'Number of items in the group.' )
65
- group_items : list [GroupItem ] = Field (
63
+ type : ItemType = Field (description = 'The type of the items in the group.' )
64
+ count : int = Field (description = 'Number of items in the group.' )
65
+ items : list [Item ] = Field (
66
66
description = ('List of items for the type found in the global search, sorted by relevance and creation time.' )
67
67
)
68
68
69
69
@classmethod
70
- def from_api_response (
71
- cls , group_type : GlobalSearchType , group_items : list [GlobalSearchResponse .Item ]
72
- ) -> 'GlobalSearchItemsGroup' :
73
- """Creates a GlobalSearchItemsGroupedByType from the API response items and a type."""
70
+ def from_api_response (cls , type : ItemType , items : list [GlobalSearchResponse .Item ]) -> 'ItemsGroup' :
71
+ """Creates a ItemsGroup from the API response items and a type."""
74
72
# filter the items by the given type to be sure
75
- group_items = [item for item in group_items if item .type == group_type ]
73
+ items = [item for item in items if item .type == type ]
76
74
return cls .model_construct (
77
- group_type = group_type ,
78
- group_count = len (group_items ),
79
- group_items = [ GlobalSearchItemsGroup . GroupItem .from_api_response (item ) for item in group_items ],
75
+ type = type ,
76
+ count = len (items ),
77
+ items = [ ItemsGroup . Item .from_api_response (item ) for item in items ],
80
78
)
81
79
82
80
83
81
class GlobalSearchOutput (BaseModel ):
84
82
"""A result of a global search query for multiple name substrings."""
85
83
86
- counts : dict [str , int ] = Field (description = 'Number of items found for each type.' )
87
- type_groups : list [ GlobalSearchItemsGroup ] = Field (description = 'List of results grouped by type .' )
84
+ counts : dict [str , int ] = Field (description = 'Number of items in total and for each type.' )
85
+ groups : dict [ ItemType , ItemsGroup ] = Field (description = 'Search results.' )
88
86
89
87
@classmethod
90
88
def from_api_responses (cls , response : GlobalSearchResponse ) -> 'GlobalSearchOutput' :
91
- """Creates a GlobalSearchResult from the API responses."""
92
- items_by_type = defaultdict (list )
89
+ """Creates a GlobalSearchOutput from the API responses."""
90
+ items_by_type : defaultdict [ ItemType , list [ GlobalSearchResponse . Item ]] = defaultdict (list )
93
91
for item in response .items :
94
92
items_by_type [item .type ].append (item )
95
93
return cls .model_construct (
96
94
counts = response .by_type , # contains counts for "total", and for each found type.
97
- type_groups = [
98
- GlobalSearchItemsGroup .from_api_response (group_type = type , group_items = items )
99
- for type , items in sorted (items_by_type .items (), key = lambda x : x [0 ])
100
- ],
95
+ groups = {
96
+ type : ItemsGroup .from_api_response (type = type , items = items ) for type , items in items_by_type .items ()
97
+ },
101
98
)
102
99
103
100
104
101
@tool_errors ()
105
102
@with_session_state ()
106
- async def global_search (
103
+ async def find_ids_by_name (
107
104
ctx : Context ,
108
- name_prefixes : Annotated [list [str ], Field (description = 'Name prefixes to look for inside entity name .' )],
109
- entity_types : Annotated [
110
- Sequence [GlobalSearchType ], Field (description = 'Optional list of keboola object types to search for .' )
105
+ name_prefixes : Annotated [list [str ], Field (description = 'Name prefixes to match against item names .' )],
106
+ item_types : Annotated [
107
+ Sequence [ItemType ], Field (description = 'Optional list of keboola item types to filter by .' )
111
108
] = tuple (),
112
109
limit : Annotated [
113
110
int ,
@@ -116,14 +113,17 @@ async def global_search(
116
113
f'{ MAX_GLOBAL_SEARCH_LIMIT } ).'
117
114
),
118
115
] = DEFAULT_GLOBAL_SEARCH_LIMIT ,
119
- offset : Annotated [int , Field (description = 'How many matching items to skip, pagination.' )] = 0 ,
120
- ) -> Annotated [GlobalSearchOutput , Field (description = 'Search results ordered by relevance, then creation time.' )]:
116
+ offset : Annotated [int , Field (description = 'Number of matching items to skip, pagination.' )] = 0 ,
117
+ ) -> Annotated [
118
+ GlobalSearchOutput ,
119
+ Field (description = 'Search results grouped by item type, ordered by relevance and creation time.' ),
120
+ ]:
121
121
"""
122
- Searches for Keboola entities by each name prefix in the production branch of the current project, potentially
123
- narrowed down by entity type, limited and paginated. Results are ordered by relevance, then creation time.
122
+ Searches for Keboola items in the production branch of the current project whose names match the given prefixes,
123
+ potentially narrowed down by item type, limited and paginated. Results are ordered by relevance, then creation time.
124
124
125
125
Considerations:
126
- - The search is purely name-based, and an entity is returned when its name or any word in the name starts with any
126
+ - The search is purely name-based, and an item is returned when its name or any word in the name starts with any
127
127
of the "name_prefixes" parameter.
128
128
"""
129
129
@@ -144,7 +144,7 @@ async def global_search(
144
144
# separately.
145
145
joined_prefixes = ' ' .join (name_prefixes )
146
146
response = await client .storage_client .global_search (
147
- query = joined_prefixes , types = entity_types , limit = limit , offset = offset
147
+ query = joined_prefixes , types = item_types , limit = limit , offset = offset
148
148
)
149
149
return GlobalSearchOutput .from_api_responses (response )
150
150
0 commit comments