Skip to content

Commit c56e2a2

Browse files
committed
option to delete credential profile
1 parent c786e19 commit c56e2a2

File tree

9 files changed

+734
-91
lines changed

9 files changed

+734
-91
lines changed

backend/composio_integration/api.py

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from .composio_service import (
1111
get_integration_service,
1212
)
13-
from .toolkit_service import ToolkitService
13+
from .toolkit_service import ToolkitService, ToolsListResponse
1414
from .composio_profile_service import ComposioProfileService, ComposioProfile
1515

1616
router = APIRouter(prefix="/composio", tags=["composio"])
@@ -21,15 +21,13 @@ def initialize(database: DBConnection):
2121
global db
2222
db = database
2323

24-
2524
class IntegrateToolkitRequest(BaseModel):
2625
toolkit_slug: str
2726
profile_name: Optional[str] = None
2827
display_name: Optional[str] = None
2928
mcp_server_name: Optional[str] = None
3029
save_as_profile: bool = True
3130

32-
3331
class IntegrationStatusResponse(BaseModel):
3432
status: str
3533
toolkit: str
@@ -40,7 +38,6 @@ class IntegrationStatusResponse(BaseModel):
4038
profile_id: Optional[str] = None
4139
redirect_url: Optional[str] = None
4240

43-
4441
class CreateProfileRequest(BaseModel):
4542
toolkit_slug: str
4643
profile_name: str
@@ -49,6 +46,10 @@ class CreateProfileRequest(BaseModel):
4946
is_default: bool = False
5047
initiation_fields: Optional[Dict[str, str]] = None
5148

49+
class ToolsListRequest(BaseModel):
50+
toolkit_slug: str
51+
limit: int = 50
52+
cursor: Optional[str] = None
5253

5354
class ProfileResponse(BaseModel):
5455
profile_id: str
@@ -406,6 +407,35 @@ async def get_toolkit_icon(
406407
raise HTTPException(status_code=500, detail="Internal server error")
407408

408409

410+
@router.post("/tools/list")
411+
async def list_toolkit_tools(
412+
request: ToolsListRequest,
413+
current_user_id: str = Depends(get_current_user_id_from_jwt)
414+
):
415+
try:
416+
logger.info(f"User {current_user_id} requesting tools for toolkit: {request.toolkit_slug}")
417+
418+
toolkit_service = ToolkitService()
419+
tools_response = await toolkit_service.get_toolkit_tools(
420+
toolkit_slug=request.toolkit_slug,
421+
limit=request.limit,
422+
cursor=request.cursor
423+
)
424+
425+
return {
426+
"success": True,
427+
"tools": [tool.dict() for tool in tools_response.items],
428+
"total_items": tools_response.total_items,
429+
"current_page": tools_response.current_page,
430+
"total_pages": tools_response.total_pages,
431+
"next_cursor": tools_response.next_cursor
432+
}
433+
434+
except Exception as e:
435+
logger.error(f"Failed to list toolkit tools for {request.toolkit_slug}: {e}", exc_info=True)
436+
raise HTTPException(status_code=500, detail=f"Failed to get toolkit tools: {str(e)}")
437+
438+
409439
@router.get("/health")
410440
async def health_check() -> Dict[str, str]:
411441
try:

backend/composio_integration/toolkit_service.py

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,31 @@ class DetailedToolkitInfo(BaseModel):
4848
base_url: Optional[str] = None
4949

5050

51+
class ParameterSchema(BaseModel):
52+
properties: Dict[str, Any] = {}
53+
required: Optional[List[str]] = None
54+
55+
56+
class ToolInfo(BaseModel):
57+
slug: str
58+
name: str
59+
description: str
60+
version: str
61+
input_parameters: ParameterSchema = ParameterSchema()
62+
output_parameters: ParameterSchema = ParameterSchema()
63+
scopes: List[str] = []
64+
tags: List[str] = []
65+
no_auth: bool = False
66+
67+
68+
class ToolsListResponse(BaseModel):
69+
items: List[ToolInfo]
70+
next_cursor: Optional[str] = None
71+
total_items: int
72+
current_page: int = 1
73+
total_pages: int = 1
74+
75+
5176
class ToolkitService:
5277
def __init__(self, api_key: Optional[str] = None):
5378
self.client = ComposioClient.get_client(api_key)
@@ -388,4 +413,80 @@ async def get_detailed_toolkit_info(self, toolkit_slug: str) -> Optional[Detaile
388413

389414
except Exception as e:
390415
logger.error(f"Failed to get detailed toolkit info for {toolkit_slug}: {e}", exc_info=True)
391-
return None
416+
return None
417+
418+
async def get_toolkit_tools(self, toolkit_slug: str, limit: int = 50, cursor: Optional[str] = None) -> ToolsListResponse:
419+
try:
420+
logger.info(f"Fetching tools for toolkit: {toolkit_slug}")
421+
422+
params = {
423+
"limit": limit,
424+
"toolkit_slug": toolkit_slug
425+
}
426+
427+
if cursor:
428+
params["cursor"] = cursor
429+
430+
tools_response = self.client.tools.list(**params)
431+
432+
if hasattr(tools_response, '__dict__'):
433+
response_data = tools_response.__dict__
434+
else:
435+
response_data = tools_response
436+
437+
items = response_data.get('items', [])
438+
439+
tools = []
440+
for item in items:
441+
if hasattr(item, '__dict__'):
442+
tool_data = item.__dict__
443+
elif hasattr(item, '_asdict'):
444+
tool_data = item._asdict()
445+
else:
446+
tool_data = item
447+
448+
input_params_raw = tool_data.get("input_parameters", {})
449+
output_params_raw = tool_data.get("output_parameters", {})
450+
451+
input_parameters = ParameterSchema()
452+
if isinstance(input_params_raw, dict):
453+
input_parameters.properties = input_params_raw.get("properties", input_params_raw)
454+
input_parameters.required = input_params_raw.get("required")
455+
456+
output_parameters = ParameterSchema()
457+
if isinstance(output_params_raw, dict):
458+
output_parameters.properties = output_params_raw.get("properties", output_params_raw)
459+
output_parameters.required = output_params_raw.get("required")
460+
461+
tool = ToolInfo(
462+
slug=tool_data.get("slug", ""),
463+
name=tool_data.get("name", ""),
464+
description=tool_data.get("description", ""),
465+
version=tool_data.get("version", "1.0.0"),
466+
input_parameters=input_parameters,
467+
output_parameters=output_parameters,
468+
scopes=tool_data.get("scopes", []),
469+
tags=tool_data.get("tags", []),
470+
no_auth=tool_data.get("no_auth", False)
471+
)
472+
tools.append(tool)
473+
474+
result = ToolsListResponse(
475+
items=tools,
476+
total_items=response_data.get("total_items", len(tools)),
477+
total_pages=response_data.get("total_pages", 1),
478+
current_page=response_data.get("current_page", 1),
479+
next_cursor=response_data.get("next_cursor")
480+
)
481+
482+
logger.info(f"Successfully fetched {len(tools)} tools for toolkit {toolkit_slug}")
483+
return result
484+
485+
except Exception as e:
486+
logger.error(f"Failed to get tools for toolkit {toolkit_slug}: {e}", exc_info=True)
487+
return ToolsListResponse(
488+
items=[],
489+
total_items=0,
490+
current_page=1,
491+
total_pages=1
492+
)

backend/credentials/api.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ def validate_config_not_empty_field(cls, v):
4242
return validate_config_not_empty(v)
4343

4444

45+
class BulkDeleteProfilesRequest(BaseModel):
46+
profile_ids: List[str]
47+
48+
4549
class CredentialResponse(BaseModel):
4650
credential_id: str
4751
mcp_qualified_name: str
@@ -64,6 +68,13 @@ class CredentialProfileResponse(BaseModel):
6468
updated_at: Optional[str] = None
6569

6670

71+
class BulkDeleteProfilesResponse(BaseModel):
72+
success: bool
73+
deleted_count: int
74+
failed_profiles: List[str] = []
75+
message: str
76+
77+
6778
class ComposioProfileSummary(BaseModel):
6879
profile_id: str
6980
profile_name: str
@@ -355,6 +366,37 @@ async def delete_credential_profile(
355366
raise HTTPException(status_code=500, detail="Internal server error")
356367

357368

369+
@router.post("/credential-profiles/bulk-delete", response_model=BulkDeleteProfilesResponse)
370+
async def bulk_delete_credential_profiles(
371+
request: BulkDeleteProfilesRequest,
372+
user_id: str = Depends(get_current_user_id_from_jwt)
373+
):
374+
try:
375+
profile_service = get_profile_service(db)
376+
deleted_count = 0
377+
failed_profiles = []
378+
for profile_id in request.profile_ids:
379+
try:
380+
success = await profile_service.delete_profile(user_id, profile_id)
381+
if success:
382+
deleted_count += 1
383+
else:
384+
failed_profiles.append(profile_id)
385+
except Exception as e:
386+
logger.error(f"Error deleting profile {profile_id}: {e}")
387+
failed_profiles.append(profile_id)
388+
389+
return BulkDeleteProfilesResponse(
390+
success=True,
391+
deleted_count=deleted_count,
392+
failed_profiles=failed_profiles,
393+
message="Bulk deletion completed"
394+
)
395+
except Exception as e:
396+
logger.error(f"Error performing bulk deletion: {e}")
397+
raise HTTPException(status_code=500, detail="Internal server error")
398+
399+
358400
@router.get("/composio-profiles", response_model=ComposioCredentialsResponse)
359401
async def get_composio_profiles(
360402
user_id: str = Depends(get_current_user_id_from_jwt)

0 commit comments

Comments
 (0)