1
- from fastapi import APIRouter , HTTPException , Body , Query
2
- from app .models .actions import ActionsResponse , Agent , Rule , MCP , Action , ActionType , ActionsListResponse
1
+ from fastapi import APIRouter , HTTPException , Query
2
+ from app .models .actions import Action , ActionType , ActionsListResponse
3
3
from app .services .actions_loader import actions_loader
4
- from app .services .mcp_installer import get_agent_content , get_rule_content , create_mcp_config
5
- from app .services .search_service import search_service
6
- from typing import List , Dict , Any , Optional
7
- import json
4
+ from typing import Optional
8
5
9
6
router = APIRouter (prefix = "/api" , tags = ["actions" ])
10
7
11
- @router .get ("/v2/ actions" , response_model = ActionsListResponse , operation_id = "get_unified_actions" )
8
+ @router .get ("/actions" , response_model = ActionsListResponse , operation_id = "get_unified_actions" )
12
9
async def get_unified_actions (
13
10
action_type : Optional [ActionType ] = Query (None , description = "Filter by action type" ),
14
11
tags : Optional [str ] = Query (None , description = "Comma-separated list of tags to filter by" ),
@@ -44,275 +41,7 @@ async def get_unified_actions(
44
41
has_more = (offset + limit ) < total
45
42
)
46
43
47
- @router .get ("/v2/actions/{action_id}" , response_model = Action , operation_id = "get_action_by_id" )
48
- async def get_action_by_id (action_id : str ):
49
- """Get a specific action by ID"""
50
- action = actions_loader .get_action_by_id (action_id )
51
- if not action :
52
- raise HTTPException (status_code = 404 , detail = f"Action not found: { action_id } " )
53
- return action
54
44
55
- @router .get ("/actions" , response_model = ActionsResponse , operation_id = "get_all_actions_endpoint" )
56
- async def get_all_actions ():
57
- """Get all available actions (agents, rules, MCPs)"""
58
- return ActionsResponse (
59
- agents = actions_loader .get_agents (),
60
- rules = actions_loader .get_rules (),
61
- mcps = actions_loader .get_mcps ()
62
- )
63
-
64
- @router .get ("/agents" , operation_id = "get_agents_endpoint" )
65
- async def get_agents (
66
- after : Optional [str ] = Query (None , description = "Cursor for pagination (use slug of last item from previous page)" ),
67
- limit : int = Query (30 , ge = 1 , le = 30 , description = "Maximum number of results (max 30)" )
68
- ):
69
- """Get available agents with tags only, limited to 30 items"""
70
- agents = actions_loader .get_agents ()
71
-
72
- # Find starting position if 'after' is provided
73
- start_idx = 0
74
- if after :
75
- for idx , agent in enumerate (agents ):
76
- if agent .slug == after :
77
- start_idx = idx + 1
78
- break
79
-
80
- # Apply limit
81
- paginated_agents = agents [start_idx :start_idx + limit ]
82
-
83
- return [
84
- {
85
- "name" : agent .name ,
86
- "display_name" : agent .display_name ,
87
- "slug" : agent .slug ,
88
- "tags" : agent .tags ,
89
- "filename" : agent .filename
90
- }
91
- for agent in paginated_agents
92
- ]
93
-
94
- @router .get ("/rules" , operation_id = "get_rules_endpoint" )
95
- async def get_rules (
96
- after : Optional [str ] = Query (None , description = "Cursor for pagination (use slug of last item from previous page)" ),
97
- limit : int = Query (30 , ge = 1 , le = 30 , description = "Maximum number of results (max 30)" )
98
- ):
99
- """Get available rules with tags only, limited to 30 items"""
100
- rules = actions_loader .get_rules ()
101
-
102
- # Find starting position if 'after' is provided
103
- start_idx = 0
104
- if after :
105
- for idx , rule in enumerate (rules ):
106
- if rule .slug == after :
107
- start_idx = idx + 1
108
- break
109
-
110
- # Apply limit
111
- paginated_rules = rules [start_idx :start_idx + limit ]
112
-
113
- return [
114
- {
115
- "name" : rule .name ,
116
- "display_name" : rule .display_name ,
117
- "slug" : rule .slug ,
118
- "tags" : rule .tags ,
119
- "filename" : rule .filename
120
- }
121
- for rule in paginated_rules
122
- ]
123
-
124
- @router .get ("/mcps" , operation_id = "get_mcps_endpoint" )
125
- async def get_mcps (
126
- after : Optional [str ] = Query (None , description = "Cursor for pagination (use name of last item from previous page)" ),
127
- limit : int = Query (30 , ge = 1 , le = 30 , description = "Maximum number of results (max 30)" )
128
- ):
129
- """Get available MCPs with tags only, limited to 30 items"""
130
- mcps = actions_loader .get_mcps ()
131
-
132
- # Find starting position if 'after' is provided
133
- start_idx = 0
134
- if after :
135
- for idx , mcp in enumerate (mcps ):
136
- if mcp .name == after :
137
- start_idx = idx + 1
138
- break
139
-
140
- # Apply limit
141
- paginated_mcps = mcps [start_idx :start_idx + limit ]
142
-
143
- return [
144
- {
145
- "name" : mcp .name ,
146
- "tags" : mcp .tags if hasattr (mcp , 'tags' ) else []
147
- }
148
- for mcp in paginated_mcps
149
- ]
150
-
151
-
152
-
153
-
154
- @router .get ("/merged-block" , operation_id = "get_merged_actions_block_endpoint" )
155
- async def get_merged_actions_block ():
156
- """Get all actions merged into a single block with metadata for frontend"""
157
- agents = actions_loader .get_agents ()
158
- rules = actions_loader .get_rules ()
159
- mcps = actions_loader .get_mcps ()
160
-
161
- # Build merged block with all actions and their metadata
162
- merged = {
163
- "agents" : [
164
- {
165
- "display_name" : agent .display_name or agent .name ,
166
- "slug" : agent .slug or agent .filename .replace ('.yaml' , '' ).replace ('.md' , '' ),
167
- "content" : agent .content or get_agent_content (agent .filename ),
168
- "filename" : agent .filename
169
- }
170
- for agent in agents
171
- ],
172
- "rules" : [
173
- {
174
- "display_name" : rule .display_name or rule .name ,
175
- "slug" : rule .slug or rule .filename .replace ('.yaml' , '' ).replace ('.md' , '' ),
176
- "content" : rule .content or get_rule_content (rule .filename ),
177
- "filename" : rule .filename
178
- }
179
- for rule in rules
180
- ],
181
- "mcps" : [
182
- {
183
- "name" : mcp .name ,
184
- "config" : mcp .config
185
- }
186
- for mcp in mcps
187
- ]
188
- }
189
-
190
- return merged
191
-
192
- @router .get ("/search/agents" , tags = ["mcp" ], operation_id = "search_agents_endpoint" )
193
- async def search_agents (
194
- query : str = Query (..., description = "Search query. Supports wildcards: * (any characters) and ? (single character)" ),
195
- limit : int = Query (10 , ge = 1 , le = 100 , description = "Maximum number of results" )
196
- ):
197
- """Search for agents by name, display_name, or content. Supports wildcard patterns with * and ?"""
198
- results = search_service .search_agents (query , limit )
199
- return {"results" : results }
200
45
201
- @router .get ("/search/rules" , tags = ["mcp" ], operation_id = "search_rules_endpoint" )
202
- async def search_rules (
203
- query : str = Query (..., description = "Search query. Supports wildcards: * (any characters) and ? (single character)" ),
204
- limit : int = Query (10 , ge = 1 , le = 100 , description = "Maximum number of results" )
205
- ):
206
- """Search for rules by name, display_name, content, tags, or author. Supports wildcard patterns with * and ?"""
207
- results = search_service .search_rules (query , limit )
208
- return {"results" : results }
209
46
210
- @router .get ("/search/mcps" , tags = ["mcp" ], operation_id = "search_mcps_endpoint" )
211
- async def search_mcps (
212
- query : str = Query (..., description = "Search query. Supports wildcards: * (any characters) and ? (single character)" ),
213
- limit : int = Query (10 , ge = 1 , le = 100 , description = "Maximum number of results" )
214
- ):
215
- """Search for MCPs by name or config content. Supports wildcard patterns with * and ?"""
216
- results = search_service .search_mcps (query , limit )
217
- return {"results" : results }
218
47
219
- @router .get ("/search" , tags = ["mcp" ], operation_id = "search_all_endpoint" )
220
- async def search_all (
221
- query : str = Query (..., description = "Search query. Supports wildcards: * (any characters) and ? (single character)" ),
222
- limit : int = Query (10 , ge = 1 , le = 100 , description = "Maximum number of results per category" )
223
- ):
224
- """Search across all types (agents, rules, MCPs). Supports wildcard patterns with * and ?"""
225
- return search_service .search_all (query , limit )
226
-
227
- @router .get ("/rules/{rule_ids}" , tags = ["mcp" ], operation_id = "get_multiple_rules_content" )
228
- async def get_multiple_rules_content (rule_ids : str ):
229
- """Get content for multiple rules by comma-separated IDs/slugs"""
230
- ids = [id .strip () for id in rule_ids .split (',' ) if id .strip ()]
231
-
232
- if not ids :
233
- raise HTTPException (status_code = 400 , detail = "No rule IDs provided" )
234
-
235
- rules = actions_loader .get_rules ()
236
- results = []
237
-
238
- for rule_id in ids :
239
- # Match by slug first, fallback to name for backward compat
240
- rule = next ((r for r in rules if (r .slug == rule_id or r .name == rule_id )), None )
241
-
242
- if rule :
243
- results .append ({
244
- "id" : rule_id ,
245
- "slug" : rule .slug ,
246
- "name" : rule .name ,
247
- "display_name" : rule .display_name ,
248
- "content" : rule .content ,
249
- "filename" : rule .filename
250
- })
251
- else :
252
- results .append ({
253
- "id" : rule_id ,
254
- "error" : f"Rule not found: { rule_id } "
255
- })
256
-
257
- return {"rules" : results }
258
-
259
- @router .get ("/agents/{agent_ids}" , tags = ["mcp" ], operation_id = "get_multiple_agents_content" )
260
- async def get_multiple_agents_content (agent_ids : str ):
261
- """Get content for multiple agents by comma-separated IDs/slugs"""
262
- ids = [id .strip () for id in agent_ids .split (',' ) if id .strip ()]
263
-
264
- if not ids :
265
- raise HTTPException (status_code = 400 , detail = "No agent IDs provided" )
266
-
267
- agents = actions_loader .get_agents ()
268
- results = []
269
-
270
- for agent_id in ids :
271
- # Match by slug first, fallback to name for backward compat
272
- agent = next ((a for a in agents if (a .slug == agent_id or a .name == agent_id )), None )
273
-
274
- if agent :
275
- results .append ({
276
- "id" : agent_id ,
277
- "slug" : agent .slug ,
278
- "name" : agent .name ,
279
- "display_name" : agent .display_name ,
280
- "content" : agent .content ,
281
- "filename" : agent .filename
282
- })
283
- else :
284
- results .append ({
285
- "id" : agent_id ,
286
- "error" : f"Agent not found: { agent_id } "
287
- })
288
-
289
- return {"agents" : results }
290
-
291
- @router .get ("/mcps/{mcp_ids}" , tags = ["mcp" ], operation_id = "get_multiple_mcps_config" )
292
- async def get_multiple_mcps_config (mcp_ids : str ):
293
- """Get config for multiple MCPs by comma-separated names. These MCPs will need to be installed on the client side (e.g. in `.mcp.json`)"""
294
- ids = [id .strip () for id in mcp_ids .split (',' ) if id .strip ()]
295
-
296
- if not ids :
297
- raise HTTPException (status_code = 400 , detail = "No MCP IDs provided" )
298
-
299
- mcps = actions_loader .get_mcps ()
300
- results = []
301
-
302
- for mcp_id in ids :
303
- # Match by name
304
- mcp = next ((m for m in mcps if m .name == mcp_id ), None )
305
-
306
- if mcp :
307
- results .append ({
308
- "id" : mcp_id ,
309
- "name" : mcp .name ,
310
- "config" : mcp .config
311
- })
312
- else :
313
- results .append ({
314
- "id" : mcp_id ,
315
- "error" : f"MCP not found: { mcp_id } "
316
- })
317
-
318
- return {"mcps" : results }
0 commit comments