glaip-sdk 0.2.2__py3-none-any.whl → 0.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. glaip_sdk/cli/auth.py +2 -1
  2. glaip_sdk/cli/commands/agents.py +51 -36
  3. glaip_sdk/cli/commands/configure.py +2 -1
  4. glaip_sdk/cli/commands/mcps.py +219 -62
  5. glaip_sdk/cli/commands/models.py +3 -5
  6. glaip_sdk/cli/commands/tools.py +27 -16
  7. glaip_sdk/cli/commands/transcripts.py +1 -1
  8. glaip_sdk/cli/constants.py +3 -0
  9. glaip_sdk/cli/display.py +1 -1
  10. glaip_sdk/cli/hints.py +58 -0
  11. glaip_sdk/cli/io.py +6 -3
  12. glaip_sdk/cli/main.py +3 -4
  13. glaip_sdk/cli/slash/agent_session.py +4 -13
  14. glaip_sdk/cli/slash/prompt.py +3 -0
  15. glaip_sdk/cli/slash/remote_runs_controller.py +566 -0
  16. glaip_sdk/cli/slash/session.py +139 -48
  17. glaip_sdk/cli/slash/tui/__init__.py +9 -0
  18. glaip_sdk/cli/slash/tui/remote_runs_app.py +632 -0
  19. glaip_sdk/cli/transcript/capture.py +1 -1
  20. glaip_sdk/cli/transcript/viewer.py +19 -678
  21. glaip_sdk/cli/update_notifier.py +2 -1
  22. glaip_sdk/cli/utils.py +228 -101
  23. glaip_sdk/cli/validators.py +5 -6
  24. glaip_sdk/client/__init__.py +2 -1
  25. glaip_sdk/client/agent_runs.py +147 -0
  26. glaip_sdk/client/agents.py +40 -22
  27. glaip_sdk/client/main.py +2 -6
  28. glaip_sdk/client/mcps.py +13 -5
  29. glaip_sdk/client/run_rendering.py +90 -111
  30. glaip_sdk/client/shared.py +21 -0
  31. glaip_sdk/client/tools.py +2 -3
  32. glaip_sdk/config/constants.py +11 -0
  33. glaip_sdk/models/__init__.py +56 -0
  34. glaip_sdk/models/agent_runs.py +117 -0
  35. glaip_sdk/models.py +8 -7
  36. glaip_sdk/rich_components.py +58 -2
  37. glaip_sdk/utils/client_utils.py +13 -0
  38. glaip_sdk/utils/display.py +23 -15
  39. glaip_sdk/utils/export.py +143 -0
  40. glaip_sdk/utils/import_export.py +6 -9
  41. glaip_sdk/utils/rendering/__init__.py +115 -1
  42. glaip_sdk/utils/rendering/formatting.py +5 -30
  43. glaip_sdk/utils/rendering/layout/__init__.py +64 -0
  44. glaip_sdk/utils/rendering/{renderer → layout}/panels.py +9 -0
  45. glaip_sdk/utils/rendering/{renderer → layout}/progress.py +70 -1
  46. glaip_sdk/utils/rendering/layout/summary.py +74 -0
  47. glaip_sdk/utils/rendering/layout/transcript.py +606 -0
  48. glaip_sdk/utils/rendering/models.py +1 -0
  49. glaip_sdk/utils/rendering/renderer/__init__.py +10 -28
  50. glaip_sdk/utils/rendering/renderer/base.py +217 -1476
  51. glaip_sdk/utils/rendering/renderer/debug.py +24 -1
  52. glaip_sdk/utils/rendering/renderer/factory.py +138 -0
  53. glaip_sdk/utils/rendering/renderer/stream.py +4 -12
  54. glaip_sdk/utils/rendering/renderer/thinking.py +273 -0
  55. glaip_sdk/utils/rendering/renderer/tool_panels.py +442 -0
  56. glaip_sdk/utils/rendering/renderer/transcript_mode.py +162 -0
  57. glaip_sdk/utils/rendering/state.py +204 -0
  58. glaip_sdk/utils/rendering/steps/__init__.py +34 -0
  59. glaip_sdk/utils/rendering/{steps.py → steps/event_processor.py} +53 -439
  60. glaip_sdk/utils/rendering/steps/format.py +176 -0
  61. glaip_sdk/utils/rendering/steps/manager.py +387 -0
  62. glaip_sdk/utils/rendering/timing.py +36 -0
  63. glaip_sdk/utils/rendering/viewer/__init__.py +21 -0
  64. glaip_sdk/utils/rendering/viewer/presenter.py +184 -0
  65. glaip_sdk/utils/resource_refs.py +26 -15
  66. glaip_sdk/utils/validation.py +13 -21
  67. {glaip_sdk-0.2.2.dist-info → glaip_sdk-0.4.0.dist-info}/METADATA +24 -2
  68. glaip_sdk-0.4.0.dist-info/RECORD +110 -0
  69. glaip_sdk-0.2.2.dist-info/RECORD +0 -87
  70. {glaip_sdk-0.2.2.dist-info → glaip_sdk-0.4.0.dist-info}/WHEEL +0 -0
  71. {glaip_sdk-0.2.2.dist-info → glaip_sdk-0.4.0.dist-info}/entry_points.txt +0 -0
glaip_sdk/cli/auth.py CHANGED
@@ -18,7 +18,8 @@ import click
18
18
  from rich.console import Console
19
19
 
20
20
  from glaip_sdk.branding import HINT_PREFIX_STYLE, WARNING_STYLE
21
- from glaip_sdk.cli.utils import command_hint, format_command_hint
21
+ from glaip_sdk.cli.hints import format_command_hint
22
+ from glaip_sdk.cli.utils import command_hint
22
23
 
23
24
 
24
25
  def prepare_authentication_export(
@@ -59,15 +59,17 @@ from glaip_sdk.cli.transcript import (
59
59
  maybe_launch_post_run_viewer,
60
60
  store_transcript_for_session,
61
61
  )
62
+ from glaip_sdk.cli.hints import in_slash_mode
62
63
  from glaip_sdk.cli.utils import (
63
64
  _fuzzy_pick_for_resources,
64
65
  build_renderer,
65
66
  coerce_to_row,
66
67
  get_client,
67
- in_slash_mode,
68
+ handle_resource_export,
68
69
  output_list,
69
70
  output_result,
70
71
  spinner_context,
72
+ with_client_and_spinner,
71
73
  )
72
74
  from glaip_sdk.cli.validators import (
73
75
  validate_agent_instruction_cli as validate_agent_instruction,
@@ -78,7 +80,7 @@ from glaip_sdk.cli.validators import (
78
80
  from glaip_sdk.cli.validators import (
79
81
  validate_timeout_cli as validate_timeout,
80
82
  )
81
- from glaip_sdk.config.constants import DEFAULT_AGENT_RUN_TIMEOUT, DEFAULT_MODEL
83
+ from glaip_sdk.config.constants import AGENT_CONFIG_FIELDS, DEFAULT_AGENT_RUN_TIMEOUT, DEFAULT_MODEL
82
84
  from glaip_sdk.exceptions import AgentTimeoutError
83
85
  from glaip_sdk.icons import ICON_AGENT
84
86
  from glaip_sdk.utils import format_datetime, is_uuid
@@ -146,10 +148,7 @@ def _build_fallback_agent_mapping(agent: Any) -> dict[str, Any]:
146
148
  "description",
147
149
  "model",
148
150
  "agent_config",
149
- "tools",
150
- "agents",
151
- "mcps",
152
- "timeout",
151
+ *[field for field in AGENT_CONFIG_FIELDS if field not in ("name", "instruction", "model")],
153
152
  "tool_configs",
154
153
  )
155
154
 
@@ -457,7 +456,6 @@ def agents_group() -> None:
457
456
  pass
458
457
 
459
458
 
460
- # pylint: disable=duplicate-code
461
459
  def _resolve_agent(
462
460
  ctx: Any,
463
461
  client: Any,
@@ -465,26 +463,38 @@ def _resolve_agent(
465
463
  select: int | None = None,
466
464
  interface_preference: str = "fuzzy",
467
465
  ) -> Any | None:
468
- """Resolve agent reference (ID or name) with ambiguity handling.
466
+ """Resolve an agent by ID or name, supporting fuzzy and questionary interfaces.
467
+
468
+ This function provides agent-specific resolution with flexible UI options.
469
+ It wraps resolve_resource_reference with agent-specific configuration, allowing
470
+ users to choose between fuzzy search and traditional questionary selection.
469
471
 
470
472
  Args:
471
- ctx: Click context object for CLI operations.
472
- client: AIP client instance for API operations.
473
- ref: Agent reference (ID or name) to resolve.
474
- select: Pre-selected agent index for non-interactive mode.
475
- interface_preference: "fuzzy" for fuzzy picker, "questionary" for up/down list.
473
+ ctx: Click context for CLI command execution.
474
+ client: AIP SDK client instance.
475
+ ref: Agent identifier (UUID or name string).
476
+ select: Pre-selected index for non-interactive resolution (1-based).
477
+ interface_preference: UI preference - "fuzzy" for search or "questionary" for list.
476
478
 
477
479
  Returns:
478
- Resolved agent object or None if not found.
480
+ Agent object when found, None when resolution fails.
479
481
  """
482
+ # Configure agent-specific resolution parameters
483
+ resolution_config = {
484
+ "resource_type": "agent",
485
+ "get_by_id": client.agents.get_agent_by_id,
486
+ "find_by_name": client.agents.find_agents,
487
+ "label": "Agent",
488
+ }
489
+ # Use agent-specific resolution with flexible interface preference
480
490
  return resolve_resource_reference(
481
491
  ctx,
482
492
  client,
483
493
  ref,
484
- "agent",
485
- client.agents.get_agent_by_id,
486
- client.agents.find_agents,
487
- "Agent",
494
+ resolution_config["resource_type"],
495
+ resolution_config["get_by_id"],
496
+ resolution_config["find_by_name"],
497
+ resolution_config["label"],
488
498
  select=select,
489
499
  interface_preference=interface_preference,
490
500
  )
@@ -514,19 +524,20 @@ def list_agents(
514
524
  ) -> None:
515
525
  """List agents with optional filtering."""
516
526
  try:
517
- client = get_client(ctx)
518
- with spinner_context(
527
+ with with_client_and_spinner(
519
528
  ctx,
520
529
  "[bold blue]Fetching agents…[/bold blue]",
521
530
  console_override=console,
522
- ):
523
- agents = client.agents.list_agents(
524
- agent_type=agent_type,
525
- framework=framework,
526
- name=name,
527
- version=version,
528
- sync_langflow_agents=sync_langflow,
529
- )
531
+ ) as client:
532
+ # Query agents with specified filters
533
+ filter_params = {
534
+ "agent_type": agent_type,
535
+ "framework": framework,
536
+ "name": name,
537
+ "version": version,
538
+ "sync_langflow_agents": sync_langflow,
539
+ }
540
+ agents = client.agents.list_agents(**filter_params)
530
541
 
531
542
  # Define table columns: (data_key, header, style, width)
532
543
  columns = [
@@ -621,28 +632,30 @@ def get(
621
632
  aip agents get my-agent --export agent.yaml # Exports complete configuration as YAML
622
633
  """
623
634
  try:
624
- client = get_client(ctx)
635
+ # Initialize API client for agent retrieval
636
+ api_client = get_client(ctx)
625
637
 
626
- # Resolve agent with ambiguity handling - use questionary interface for traditional UX
627
- agent = _resolve_agent(ctx, client, agent_ref, select, interface_preference="questionary")
638
+ # Resolve agent reference using questionary interface for better UX
639
+ agent = _resolve_agent(ctx, api_client, agent_ref, select, interface_preference="questionary")
628
640
 
629
- # Handle export option
630
- if export:
631
- from glaip_sdk.cli.utils import handle_resource_export
641
+ if not agent:
642
+ raise click.ClickException(f"Agent '{agent_ref}' not found")
632
643
 
644
+ # Handle export option if requested
645
+ if export:
633
646
  handle_resource_export(
634
647
  ctx,
635
648
  agent,
636
649
  Path(export),
637
650
  resource_type="agent",
638
- get_by_id_func=client.agents.get_agent_by_id,
651
+ get_by_id_func=api_client.agents.get_agent_by_id,
639
652
  console_override=console,
640
653
  )
641
654
 
642
655
  # Display full agent details using the standardized helper
643
656
  _display_agent_details(
644
657
  ctx,
645
- client,
658
+ api_client,
646
659
  agent,
647
660
  instruction_preview_limit=instruction_preview,
648
661
  )
@@ -650,6 +663,8 @@ def get(
650
663
  # Show run suggestions via centralized display helper
651
664
  handle_rich_output(ctx, display_agent_run_suggestions(agent))
652
665
 
666
+ except click.ClickException:
667
+ raise
653
668
  except Exception as e:
654
669
  raise click.ClickException(str(e)) from e
655
670
 
@@ -23,7 +23,8 @@ from glaip_sdk.branding import (
23
23
  )
24
24
  from glaip_sdk.cli.config import CONFIG_FILE, load_config, save_config
25
25
  from glaip_sdk.cli.rich_helpers import markup_text
26
- from glaip_sdk.cli.utils import command_hint, format_command_hint, sdk_version
26
+ from glaip_sdk.cli.hints import format_command_hint
27
+ from glaip_sdk.cli.utils import command_hint, sdk_version
27
28
  from glaip_sdk.icons import ICON_TOOL
28
29
  from glaip_sdk.rich_components import AIPTable
29
30
 
@@ -43,10 +43,13 @@ from glaip_sdk.cli.resolution import resolve_resource_reference
43
43
  from glaip_sdk.cli.rich_helpers import print_markup
44
44
  from glaip_sdk.cli.utils import (
45
45
  coerce_to_row,
46
+ fetch_resource_for_export,
47
+ format_datetime_fields,
46
48
  get_client,
47
49
  output_list,
48
50
  output_result,
49
51
  spinner_context,
52
+ with_client_and_spinner,
50
53
  )
51
54
  from glaip_sdk.config.constants import (
52
55
  DEFAULT_MCP_TYPE,
@@ -60,6 +63,7 @@ from glaip_sdk.utils.serialization import (
60
63
  )
61
64
 
62
65
  console = Console()
66
+ MAX_DESCRIPTION_LEN = 50
63
67
 
64
68
 
65
69
  def _is_sensitive_data(val: Any) -> bool:
@@ -298,28 +302,37 @@ def mcps_group() -> None:
298
302
  pass
299
303
 
300
304
 
301
- def _resolve_mcp(ctx: Any, client: Any, ref: str, select: int | None = None) -> Any | None: # pylint: disable=duplicate-code
302
- """Resolve MCP reference (ID or name) with ambiguity handling.
305
+ def _resolve_mcp(ctx: Any, client: Any, ref: str, select: int | None = None) -> Any | None:
306
+ """Resolve an MCP server by ID or name, with interactive selection support.
307
+
308
+ This function provides MCP-specific resolution logic. It delegates to
309
+ resolve_resource_reference for MCP-specific resolution, supporting UUID
310
+ lookups and name-based fuzzy matching.
303
311
 
304
312
  Args:
305
- ctx: Click context object
306
- client: API client instance
307
- ref: MCP reference (ID or name)
308
- select: Index to select when multiple matches found
313
+ ctx: Click context for command execution.
314
+ client: API client for backend operations.
315
+ ref: MCP identifier (UUID or name string).
316
+ select: Optional selection index when multiple MCPs match (1-based).
309
317
 
310
318
  Returns:
311
- MCP object if found, None otherwise
319
+ MCP instance if resolution succeeds, None if not found.
312
320
 
313
321
  Raises:
314
- ClickException: If MCP not found or selection invalid
322
+ click.ClickException: When resolution fails or selection is invalid.
315
323
  """
324
+ # Configure MCP-specific resolution functions
325
+ mcp_client = client.mcps
326
+ get_by_id_func = mcp_client.get_mcp_by_id
327
+ find_by_name_func = mcp_client.find_mcps
328
+ # Use MCP-specific resolution with standard fuzzy matching
316
329
  return resolve_resource_reference(
317
330
  ctx,
318
331
  client,
319
332
  ref,
320
333
  "mcp",
321
- client.mcps.get_mcp_by_id,
322
- client.mcps.find_mcps,
334
+ get_by_id_func,
335
+ find_by_name_func,
323
336
  "MCP",
324
337
  select=select,
325
338
  )
@@ -512,12 +525,11 @@ def list_mcps(ctx: Any) -> None:
512
525
  ClickException: If API request fails
513
526
  """
514
527
  try:
515
- client = get_client(ctx)
516
- with spinner_context(
528
+ with with_client_and_spinner(
517
529
  ctx,
518
530
  "[bold blue]Fetching MCPs…[/bold blue]",
519
531
  console_override=console,
520
- ):
532
+ ) as client:
521
533
  mcps = client.mcps.list_mcps()
522
534
 
523
535
  # Define table columns: (data_key, header, style, width)
@@ -612,8 +624,10 @@ def create(
612
624
  aip mcps create --import mcp-export.json --name new-name --transport sse
613
625
  """
614
626
  try:
615
- client = get_client(ctx)
627
+ # Get API client instance for MCP operations
628
+ api_client = get_client(ctx)
616
629
 
630
+ # Process import file if specified, otherwise use None
617
631
  import_payload = _load_import_ready_payload(import_file) if import_file is not None else None
618
632
 
619
633
  merged_payload, missing_fields = _merge_import_payload(
@@ -657,7 +671,7 @@ def create(
657
671
  if mcp_metadata is not None:
658
672
  create_kwargs["mcp_metadata"] = mcp_metadata
659
673
 
660
- mcp = client.mcps.create_mcp(**create_kwargs)
674
+ mcp = api_client.mcps.create_mcp(**create_kwargs)
661
675
 
662
676
  # Handle JSON output
663
677
  handle_json_output(ctx, mcp.model_dump())
@@ -704,7 +718,6 @@ def _handle_mcp_export(
704
718
  detected_format = detect_export_format(export_path)
705
719
 
706
720
  # Always export comprehensive data - re-fetch with full details
707
- from glaip_sdk.cli.utils import fetch_resource_for_export
708
721
 
709
722
  mcp = fetch_resource_for_export(
710
723
  ctx,
@@ -783,8 +796,6 @@ def _display_mcp_details(ctx: Any, client: Any, mcp: Any) -> None:
783
796
 
784
797
  if raw_mcp_data:
785
798
  # Use raw API data - this preserves ALL fields
786
- from glaip_sdk.cli.utils import format_datetime_fields
787
-
788
799
  formatted_data = format_datetime_fields(raw_mcp_data)
789
800
 
790
801
  output_result(
@@ -869,64 +880,210 @@ def get(
869
880
  raise click.ClickException(str(e)) from e
870
881
 
871
882
 
883
+ def _get_tools_from_config(ctx: Any, client: Any, config_file: str) -> tuple[list[dict[str, Any]], str]:
884
+ """Get tools from MCP config file.
885
+
886
+ Args:
887
+ ctx: Click context
888
+ client: GlaIP client instance
889
+ config_file: Path to config file
890
+
891
+ Returns:
892
+ Tuple of (tools list, title string)
893
+ """
894
+ config_data = load_resource_from_file_with_validation(Path(config_file), "MCP config")
895
+
896
+ # Validate config structure
897
+ transport = config_data.get("transport")
898
+ if "config" not in config_data:
899
+ raise click.ClickException("Invalid MCP config: missing 'config' section in the file.")
900
+ config_data["config"] = validate_mcp_config_structure(
901
+ config_data["config"],
902
+ transport=transport,
903
+ source=config_file,
904
+ )
905
+
906
+ # Get tools from config without saving
907
+ with spinner_context(
908
+ ctx,
909
+ "[bold blue]Fetching tools from config…[/bold blue]",
910
+ console_override=console,
911
+ ):
912
+ tools = client.mcps.get_mcp_tools_from_config(config_data)
913
+
914
+ title = f"{ICON_TOOL} Tools from config: {Path(config_file).name}"
915
+ return tools, title
916
+
917
+
918
+ def _get_tools_from_mcp(ctx: Any, client: Any, mcp_ref: str | None) -> tuple[list[dict[str, Any]], str]:
919
+ """Get tools from saved MCP.
920
+
921
+ Args:
922
+ ctx: Click context
923
+ client: GlaIP client instance
924
+ mcp_ref: MCP reference (ID or name)
925
+
926
+ Returns:
927
+ Tuple of (tools list, title string)
928
+ """
929
+ mcp = _resolve_mcp(ctx, client, mcp_ref)
930
+
931
+ with spinner_context(
932
+ ctx,
933
+ "[bold blue]Fetching MCP tools…[/bold blue]",
934
+ console_override=console,
935
+ ):
936
+ tools = client.mcps.get_mcp_tools(mcp.id)
937
+
938
+ title = f"{ICON_TOOL} Tools from MCP: {mcp.name}"
939
+ return tools, title
940
+
941
+
942
+ def _output_tool_names(ctx: Any, tools: list[dict[str, Any]]) -> None:
943
+ """Output only tool names.
944
+
945
+ Args:
946
+ ctx: Click context
947
+ tools: List of tool dictionaries
948
+ """
949
+ view = get_ctx_value(ctx, "view", "rich")
950
+ tool_names = [tool.get("name", "N/A") for tool in tools]
951
+
952
+ if view == "json":
953
+ handle_json_output(ctx, tool_names)
954
+ elif view == "plain":
955
+ if tool_names:
956
+ for name in tool_names:
957
+ console.print(name, markup=False)
958
+ console.print(f"Total: {len(tool_names)} tools", markup=False)
959
+ else:
960
+ console.print("No tools found", markup=False)
961
+ else:
962
+ if tool_names:
963
+ for name in tool_names:
964
+ console.print(name)
965
+ console.print(f"[dim]Total: {len(tool_names)} tools[/dim]")
966
+ else:
967
+ console.print("[yellow]No tools found[/yellow]")
968
+
969
+
970
+ def _transform_tool(tool: dict[str, Any]) -> dict[str, Any]:
971
+ """Transform a tool dictionary to a display row dictionary.
972
+
973
+ Args:
974
+ tool: Tool dictionary to transform.
975
+
976
+ Returns:
977
+ Dictionary with name and description fields.
978
+ """
979
+ description = tool.get("description", "N/A")
980
+ if len(description) > MAX_DESCRIPTION_LEN:
981
+ description = description[: MAX_DESCRIPTION_LEN - 3] + "..."
982
+ return {
983
+ "name": tool.get("name", "N/A"),
984
+ "description": description,
985
+ }
986
+
987
+
988
+ def _output_tools_table(ctx: Any, tools: list[dict[str, Any]], title: str) -> None:
989
+ """Output tools in table format.
990
+
991
+ Args:
992
+ ctx: Click context
993
+ tools: List of tool dictionaries
994
+ title: Table title
995
+ """
996
+ columns = [
997
+ ("name", "Name", ACCENT_STYLE, None),
998
+ ("description", "Description", INFO, 50),
999
+ ]
1000
+
1001
+ output_list(
1002
+ ctx,
1003
+ tools,
1004
+ title,
1005
+ columns,
1006
+ _transform_tool,
1007
+ )
1008
+
1009
+
1010
+ def _validate_tool_command_args(mcp_ref: str | None, config_file: str | None) -> None:
1011
+ """Validate that exactly one of mcp_ref or config_file is provided.
1012
+
1013
+ Args:
1014
+ mcp_ref: MCP reference (ID or name)
1015
+ config_file: Path to config file
1016
+
1017
+ Raises:
1018
+ ClickException: If validation fails
1019
+ """
1020
+ if not mcp_ref and not config_file:
1021
+ raise click.ClickException(
1022
+ "Either MCP_REF or --from-config must be provided.\n"
1023
+ "Examples:\n"
1024
+ " aip mcps tools <MCP_ID>\n"
1025
+ " aip mcps tools --from-config mcp-config.json"
1026
+ )
1027
+ if mcp_ref and config_file:
1028
+ raise click.ClickException(
1029
+ "Cannot use both MCP_REF and --from-config at the same time.\n"
1030
+ "Use either:\n"
1031
+ " aip mcps tools <MCP_ID>\n"
1032
+ " aip mcps tools --from-config mcp-config.json"
1033
+ )
1034
+
1035
+
872
1036
  @mcps_group.command("tools")
873
- @click.argument("mcp_ref")
1037
+ @click.argument("mcp_ref", required=False)
1038
+ @click.option(
1039
+ "--from-config",
1040
+ "--config",
1041
+ "config_file",
1042
+ type=click.Path(exists=True, dir_okay=False),
1043
+ help="Get tools from MCP config file without saving to DB (JSON or YAML)",
1044
+ )
1045
+ @click.option(
1046
+ "--names-only",
1047
+ is_flag=True,
1048
+ help="Show only tool names (useful for allowed_tools config)",
1049
+ )
874
1050
  @output_flags()
875
1051
  @click.pass_context
876
- def list_tools(ctx: Any, mcp_ref: str) -> None:
877
- """List tools available from a specific MCP.
1052
+ def list_tools(ctx: Any, mcp_ref: str | None, config_file: str | None, names_only: bool) -> None:
1053
+ """List tools available from a specific MCP or config file.
878
1054
 
879
1055
  Args:
880
1056
  ctx: Click context containing output format preferences
881
- mcp_ref: MCP reference (ID or name)
1057
+ mcp_ref: MCP reference (ID or name) - required if --from-config not used
1058
+ config_file: Path to MCP config file - alternative to mcp_ref
1059
+ names_only: Show only tool names instead of full table
882
1060
 
883
1061
  Raises:
884
1062
  ClickException: If MCP not found or tools fetch fails
885
- """
886
- try:
887
- client = get_client(ctx)
888
-
889
- # Resolve MCP using helper function
890
- mcp = _resolve_mcp(ctx, client, mcp_ref)
891
1063
 
892
- # Get tools from MCP
893
- with spinner_context(
894
- ctx,
895
- "[bold blue]Fetching MCP tools…[/bold blue]",
896
- console_override=console,
897
- ):
898
- tools = client.mcps.get_mcp_tools(mcp.id)
899
-
900
- # Define table columns: (data_key, header, style, width)
901
- columns = [
902
- ("name", "Name", ACCENT_STYLE, None),
903
- ("description", "Description", INFO, 50),
904
- ]
1064
+ Examples:
1065
+ Get tools from saved MCP:
1066
+ aip mcps tools <MCP_ID>
905
1067
 
906
- # Transform function for safe dictionary access
907
- def transform_tool(tool: dict[str, Any]) -> dict[str, Any]:
908
- """Transform a tool dictionary to a display row dictionary.
1068
+ Get tools from config file (without saving to DB):
1069
+ aip mcps tools --from-config mcp-config.json
909
1070
 
910
- Args:
911
- tool: Tool dictionary to transform.
1071
+ Get just tool names for allowed_tools config:
1072
+ aip mcps tools <MCP_ID> --names-only
1073
+ """
1074
+ try:
1075
+ _validate_tool_command_args(mcp_ref, config_file)
1076
+ client = get_client(ctx)
912
1077
 
913
- Returns:
914
- Dictionary with name and description fields.
915
- """
916
- return {
917
- "name": tool.get("name", "N/A"),
918
- "description": tool.get("description", "N/A")[:47] + "..."
919
- if len(tool.get("description", "")) > 47
920
- else tool.get("description", "N/A"),
921
- }
1078
+ if config_file:
1079
+ tools, title = _get_tools_from_config(ctx, client, config_file)
1080
+ else:
1081
+ tools, title = _get_tools_from_mcp(ctx, client, mcp_ref)
922
1082
 
923
- output_list(
924
- ctx,
925
- tools,
926
- f"{ICON_TOOL} Tools from MCP: {mcp.name}",
927
- columns,
928
- transform_tool,
929
- )
1083
+ if names_only:
1084
+ _output_tool_names(ctx, tools)
1085
+ else:
1086
+ _output_tools_table(ctx, tools, title)
930
1087
 
931
1088
  except Exception as e:
932
1089
  raise click.ClickException(str(e)) from e
@@ -12,9 +12,8 @@ from rich.console import Console
12
12
  from glaip_sdk.branding import ACCENT_STYLE, INFO, SUCCESS
13
13
  from glaip_sdk.cli.context import output_flags
14
14
  from glaip_sdk.cli.utils import (
15
- get_client,
16
15
  output_list,
17
- spinner_context,
16
+ with_client_and_spinner,
18
17
  )
19
18
 
20
19
  console = Console()
@@ -32,12 +31,11 @@ def models_group() -> None:
32
31
  def list_models(ctx: Any) -> None:
33
32
  """List available language models."""
34
33
  try:
35
- client = get_client(ctx)
36
- with spinner_context(
34
+ with with_client_and_spinner(
37
35
  ctx,
38
36
  "[bold blue]Fetching language models…[/bold blue]",
39
37
  console_override=console,
40
- ):
38
+ ) as client:
41
39
  models = client.list_language_models()
42
40
 
43
41
  # Define table columns: (data_key, header, style, width)