glaip-sdk 0.1.3__py3-none-any.whl → 0.6.19__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 (146) hide show
  1. glaip_sdk/__init__.py +44 -4
  2. glaip_sdk/_version.py +9 -0
  3. glaip_sdk/agents/__init__.py +27 -0
  4. glaip_sdk/agents/base.py +1196 -0
  5. glaip_sdk/branding.py +13 -0
  6. glaip_sdk/cli/account_store.py +540 -0
  7. glaip_sdk/cli/auth.py +254 -15
  8. glaip_sdk/cli/commands/__init__.py +2 -2
  9. glaip_sdk/cli/commands/accounts.py +746 -0
  10. glaip_sdk/cli/commands/agents.py +213 -73
  11. glaip_sdk/cli/commands/common_config.py +104 -0
  12. glaip_sdk/cli/commands/configure.py +729 -113
  13. glaip_sdk/cli/commands/mcps.py +241 -72
  14. glaip_sdk/cli/commands/models.py +11 -5
  15. glaip_sdk/cli/commands/tools.py +49 -57
  16. glaip_sdk/cli/commands/transcripts.py +755 -0
  17. glaip_sdk/cli/config.py +48 -4
  18. glaip_sdk/cli/constants.py +38 -0
  19. glaip_sdk/cli/context.py +8 -0
  20. glaip_sdk/cli/core/__init__.py +79 -0
  21. glaip_sdk/cli/core/context.py +124 -0
  22. glaip_sdk/cli/core/output.py +851 -0
  23. glaip_sdk/cli/core/prompting.py +649 -0
  24. glaip_sdk/cli/core/rendering.py +187 -0
  25. glaip_sdk/cli/display.py +35 -19
  26. glaip_sdk/cli/hints.py +57 -0
  27. glaip_sdk/cli/io.py +6 -3
  28. glaip_sdk/cli/main.py +241 -121
  29. glaip_sdk/cli/masking.py +21 -33
  30. glaip_sdk/cli/pager.py +9 -10
  31. glaip_sdk/cli/parsers/__init__.py +1 -3
  32. glaip_sdk/cli/slash/__init__.py +0 -9
  33. glaip_sdk/cli/slash/accounts_controller.py +578 -0
  34. glaip_sdk/cli/slash/accounts_shared.py +75 -0
  35. glaip_sdk/cli/slash/agent_session.py +62 -21
  36. glaip_sdk/cli/slash/prompt.py +21 -0
  37. glaip_sdk/cli/slash/remote_runs_controller.py +566 -0
  38. glaip_sdk/cli/slash/session.py +771 -140
  39. glaip_sdk/cli/slash/tui/__init__.py +9 -0
  40. glaip_sdk/cli/slash/tui/accounts.tcss +86 -0
  41. glaip_sdk/cli/slash/tui/accounts_app.py +876 -0
  42. glaip_sdk/cli/slash/tui/background_tasks.py +72 -0
  43. glaip_sdk/cli/slash/tui/loading.py +58 -0
  44. glaip_sdk/cli/slash/tui/remote_runs_app.py +628 -0
  45. glaip_sdk/cli/transcript/__init__.py +12 -52
  46. glaip_sdk/cli/transcript/cache.py +255 -44
  47. glaip_sdk/cli/transcript/capture.py +27 -1
  48. glaip_sdk/cli/transcript/history.py +815 -0
  49. glaip_sdk/cli/transcript/viewer.py +72 -499
  50. glaip_sdk/cli/update_notifier.py +14 -5
  51. glaip_sdk/cli/utils.py +243 -1252
  52. glaip_sdk/cli/validators.py +5 -6
  53. glaip_sdk/client/__init__.py +2 -1
  54. glaip_sdk/client/_agent_payloads.py +45 -9
  55. glaip_sdk/client/agent_runs.py +147 -0
  56. glaip_sdk/client/agents.py +291 -35
  57. glaip_sdk/client/base.py +1 -0
  58. glaip_sdk/client/main.py +19 -10
  59. glaip_sdk/client/mcps.py +122 -12
  60. glaip_sdk/client/run_rendering.py +466 -89
  61. glaip_sdk/client/shared.py +21 -0
  62. glaip_sdk/client/tools.py +155 -10
  63. glaip_sdk/config/constants.py +11 -0
  64. glaip_sdk/hitl/__init__.py +15 -0
  65. glaip_sdk/hitl/local.py +151 -0
  66. glaip_sdk/mcps/__init__.py +21 -0
  67. glaip_sdk/mcps/base.py +345 -0
  68. glaip_sdk/models/__init__.py +90 -0
  69. glaip_sdk/models/agent.py +47 -0
  70. glaip_sdk/models/agent_runs.py +116 -0
  71. glaip_sdk/models/common.py +42 -0
  72. glaip_sdk/models/mcp.py +33 -0
  73. glaip_sdk/models/tool.py +33 -0
  74. glaip_sdk/payload_schemas/__init__.py +1 -13
  75. glaip_sdk/registry/__init__.py +55 -0
  76. glaip_sdk/registry/agent.py +164 -0
  77. glaip_sdk/registry/base.py +139 -0
  78. glaip_sdk/registry/mcp.py +253 -0
  79. glaip_sdk/registry/tool.py +232 -0
  80. glaip_sdk/rich_components.py +58 -2
  81. glaip_sdk/runner/__init__.py +59 -0
  82. glaip_sdk/runner/base.py +84 -0
  83. glaip_sdk/runner/deps.py +112 -0
  84. glaip_sdk/runner/langgraph.py +870 -0
  85. glaip_sdk/runner/mcp_adapter/__init__.py +13 -0
  86. glaip_sdk/runner/mcp_adapter/base_mcp_adapter.py +43 -0
  87. glaip_sdk/runner/mcp_adapter/langchain_mcp_adapter.py +257 -0
  88. glaip_sdk/runner/mcp_adapter/mcp_config_builder.py +95 -0
  89. glaip_sdk/runner/tool_adapter/__init__.py +18 -0
  90. glaip_sdk/runner/tool_adapter/base_tool_adapter.py +44 -0
  91. glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +219 -0
  92. glaip_sdk/tools/__init__.py +22 -0
  93. glaip_sdk/tools/base.py +435 -0
  94. glaip_sdk/utils/__init__.py +58 -12
  95. glaip_sdk/utils/a2a/__init__.py +34 -0
  96. glaip_sdk/utils/a2a/event_processor.py +188 -0
  97. glaip_sdk/utils/bundler.py +267 -0
  98. glaip_sdk/utils/client.py +111 -0
  99. glaip_sdk/utils/client_utils.py +39 -7
  100. glaip_sdk/utils/datetime_helpers.py +58 -0
  101. glaip_sdk/utils/discovery.py +78 -0
  102. glaip_sdk/utils/display.py +23 -15
  103. glaip_sdk/utils/export.py +143 -0
  104. glaip_sdk/utils/general.py +0 -33
  105. glaip_sdk/utils/import_export.py +12 -7
  106. glaip_sdk/utils/import_resolver.py +492 -0
  107. glaip_sdk/utils/instructions.py +101 -0
  108. glaip_sdk/utils/rendering/__init__.py +115 -1
  109. glaip_sdk/utils/rendering/formatting.py +5 -30
  110. glaip_sdk/utils/rendering/layout/__init__.py +64 -0
  111. glaip_sdk/utils/rendering/{renderer → layout}/panels.py +9 -0
  112. glaip_sdk/utils/rendering/{renderer → layout}/progress.py +70 -1
  113. glaip_sdk/utils/rendering/layout/summary.py +74 -0
  114. glaip_sdk/utils/rendering/layout/transcript.py +606 -0
  115. glaip_sdk/utils/rendering/models.py +1 -0
  116. glaip_sdk/utils/rendering/renderer/__init__.py +9 -47
  117. glaip_sdk/utils/rendering/renderer/base.py +275 -1476
  118. glaip_sdk/utils/rendering/renderer/debug.py +26 -20
  119. glaip_sdk/utils/rendering/renderer/factory.py +138 -0
  120. glaip_sdk/utils/rendering/renderer/stream.py +4 -12
  121. glaip_sdk/utils/rendering/renderer/thinking.py +273 -0
  122. glaip_sdk/utils/rendering/renderer/tool_panels.py +442 -0
  123. glaip_sdk/utils/rendering/renderer/transcript_mode.py +162 -0
  124. glaip_sdk/utils/rendering/state.py +204 -0
  125. glaip_sdk/utils/rendering/steps/__init__.py +34 -0
  126. glaip_sdk/utils/rendering/{steps.py → steps/event_processor.py} +53 -440
  127. glaip_sdk/utils/rendering/steps/format.py +176 -0
  128. glaip_sdk/utils/rendering/steps/manager.py +387 -0
  129. glaip_sdk/utils/rendering/timing.py +36 -0
  130. glaip_sdk/utils/rendering/viewer/__init__.py +21 -0
  131. glaip_sdk/utils/rendering/viewer/presenter.py +184 -0
  132. glaip_sdk/utils/resource_refs.py +25 -13
  133. glaip_sdk/utils/runtime_config.py +425 -0
  134. glaip_sdk/utils/serialization.py +18 -0
  135. glaip_sdk/utils/sync.py +142 -0
  136. glaip_sdk/utils/tool_detection.py +33 -0
  137. glaip_sdk/utils/tool_storage_provider.py +140 -0
  138. glaip_sdk/utils/validation.py +16 -24
  139. {glaip_sdk-0.1.3.dist-info → glaip_sdk-0.6.19.dist-info}/METADATA +56 -21
  140. glaip_sdk-0.6.19.dist-info/RECORD +163 -0
  141. {glaip_sdk-0.1.3.dist-info → glaip_sdk-0.6.19.dist-info}/WHEEL +2 -1
  142. glaip_sdk-0.6.19.dist-info/entry_points.txt +2 -0
  143. glaip_sdk-0.6.19.dist-info/top_level.txt +1 -0
  144. glaip_sdk/models.py +0 -240
  145. glaip_sdk-0.1.3.dist-info/RECORD +0 -83
  146. glaip_sdk-0.1.3.dist-info/entry_points.txt +0 -3
@@ -43,17 +43,19 @@ 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,
53
56
  )
54
57
  from glaip_sdk.icons import ICON_TOOL
55
58
  from glaip_sdk.rich_components import AIPPanel
56
- from glaip_sdk.utils import format_datetime
57
59
  from glaip_sdk.utils.import_export import convert_export_to_import_format
58
60
  from glaip_sdk.utils.serialization import (
59
61
  build_mcp_export_payload,
@@ -61,6 +63,7 @@ from glaip_sdk.utils.serialization import (
61
63
  )
62
64
 
63
65
  console = Console()
66
+ MAX_DESCRIPTION_LEN = 50
64
67
 
65
68
 
66
69
  def _is_sensitive_data(val: Any) -> bool:
@@ -300,27 +303,36 @@ def mcps_group() -> None:
300
303
 
301
304
 
302
305
  def _resolve_mcp(ctx: Any, client: Any, ref: str, select: int | None = None) -> Any | None:
303
- """Resolve MCP reference (ID or name) with ambiguity handling.
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.
304
311
 
305
312
  Args:
306
- ctx: Click context object
307
- client: API client instance
308
- ref: MCP reference (ID or name)
309
- 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).
310
317
 
311
318
  Returns:
312
- MCP object if found, None otherwise
319
+ MCP instance if resolution succeeds, None if not found.
313
320
 
314
321
  Raises:
315
- ClickException: If MCP not found or selection invalid
322
+ click.ClickException: When resolution fails or selection is invalid.
316
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
317
329
  return resolve_resource_reference(
318
330
  ctx,
319
331
  client,
320
332
  ref,
321
333
  "mcp",
322
- client.mcps.get_mcp_by_id,
323
- client.mcps.find_mcps,
334
+ get_by_id_func,
335
+ find_by_name_func,
324
336
  "MCP",
325
337
  select=select,
326
338
  )
@@ -513,12 +525,11 @@ def list_mcps(ctx: Any) -> None:
513
525
  ClickException: If API request fails
514
526
  """
515
527
  try:
516
- client = get_client(ctx)
517
- with spinner_context(
528
+ with with_client_and_spinner(
518
529
  ctx,
519
530
  "[bold blue]Fetching MCPs…[/bold blue]",
520
531
  console_override=console,
521
- ):
532
+ ) as client:
522
533
  mcps = client.mcps.list_mcps()
523
534
 
524
535
  # Define table columns: (data_key, header, style, width)
@@ -530,6 +541,14 @@ def list_mcps(ctx: Any) -> None:
530
541
 
531
542
  # Transform function for safe dictionary access
532
543
  def transform_mcp(mcp: Any) -> dict[str, Any]:
544
+ """Transform an MCP object to a display row dictionary.
545
+
546
+ Args:
547
+ mcp: MCP object to transform.
548
+
549
+ Returns:
550
+ Dictionary with id, name, and config fields.
551
+ """
533
552
  row = coerce_to_row(mcp, ["id", "name", "config"])
534
553
  # Ensure id is always a string
535
554
  row["id"] = str(row["id"])
@@ -575,7 +594,7 @@ def create(
575
594
  auth: str | None,
576
595
  import_file: str | None,
577
596
  ) -> None:
578
- """Create a new MCP with specified configuration.
597
+ r"""Create a new MCP with specified configuration.
579
598
 
580
599
  You can create an MCP by providing all parameters via CLI options, or by
581
600
  importing from a file and optionally overriding specific fields.
@@ -593,6 +612,7 @@ def create(
593
612
  Raises:
594
613
  ClickException: If JSON parsing fails or API request fails
595
614
 
615
+ \b
596
616
  Examples:
597
617
  Create from CLI options:
598
618
  aip mcps create --name my-mcp --transport http --config '{"url": "https://api.example.com"}'
@@ -604,8 +624,10 @@ def create(
604
624
  aip mcps create --import mcp-export.json --name new-name --transport sse
605
625
  """
606
626
  try:
607
- client = get_client(ctx)
627
+ # Get API client instance for MCP operations
628
+ api_client = get_client(ctx)
608
629
 
630
+ # Process import file if specified, otherwise use None
609
631
  import_payload = _load_import_ready_payload(import_file) if import_file is not None else None
610
632
 
611
633
  merged_payload, missing_fields = _merge_import_payload(
@@ -649,7 +671,7 @@ def create(
649
671
  if mcp_metadata is not None:
650
672
  create_kwargs["mcp_metadata"] = mcp_metadata
651
673
 
652
- mcp = client.mcps.create_mcp(**create_kwargs)
674
+ mcp = api_client.mcps.create_mcp(**create_kwargs)
653
675
 
654
676
  # Handle JSON output
655
677
  handle_json_output(ctx, mcp.model_dump())
@@ -696,19 +718,14 @@ def _handle_mcp_export(
696
718
  detected_format = detect_export_format(export_path)
697
719
 
698
720
  # Always export comprehensive data - re-fetch with full details
699
- try:
700
- with spinner_context(
701
- ctx,
702
- "[bold blue]Fetching complete MCP details…[/bold blue]",
703
- console_override=console,
704
- ):
705
- mcp = client.mcps.get_mcp_by_id(mcp.id)
706
- except Exception as e:
707
- print_markup(
708
- f"[{WARNING_STYLE}]⚠️ Could not fetch full MCP details: {e}[/]",
709
- console=console,
710
- )
711
- print_markup(f"[{WARNING_STYLE}]⚠️ Proceeding with available data[/]", console=console)
721
+
722
+ mcp = fetch_resource_for_export(
723
+ ctx,
724
+ mcp,
725
+ resource_type="MCP",
726
+ get_by_id_func=client.mcps.get_mcp_by_id,
727
+ console_override=console,
728
+ )
712
729
 
713
730
  # Determine if we should prompt for secrets
714
731
  prompt_for_secrets = not no_auth_prompt and sys.stdin.isatty()
@@ -779,11 +796,7 @@ def _display_mcp_details(ctx: Any, client: Any, mcp: Any) -> None:
779
796
 
780
797
  if raw_mcp_data:
781
798
  # Use raw API data - this preserves ALL fields
782
- formatted_data = raw_mcp_data.copy()
783
- if "created_at" in formatted_data:
784
- formatted_data["created_at"] = format_datetime(formatted_data["created_at"])
785
- if "updated_at" in formatted_data:
786
- formatted_data["updated_at"] = format_datetime(formatted_data["updated_at"])
799
+ formatted_data = format_datetime_fields(raw_mcp_data)
787
800
 
788
801
  output_result(
789
802
  ctx,
@@ -832,7 +845,7 @@ def get(
832
845
  no_auth_prompt: bool,
833
846
  auth_placeholder: str,
834
847
  ) -> None:
835
- """Get MCP details and optionally export configuration to file.
848
+ r"""Get MCP details and optionally export configuration to file.
836
849
 
837
850
  Args:
838
851
  ctx: Click context containing output format preferences
@@ -844,6 +857,7 @@ def get(
844
857
  Raises:
845
858
  ClickException: If MCP not found or export fails
846
859
 
860
+ \b
847
861
  Examples:
848
862
  aip mcps get my-mcp
849
863
  aip mcps get my-mcp --export mcp.json # Export as JSON
@@ -866,56 +880,210 @@ def get(
866
880
  raise click.ClickException(str(e)) from e
867
881
 
868
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
+
869
1036
  @mcps_group.command("tools")
870
- @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
+ )
871
1050
  @output_flags()
872
1051
  @click.pass_context
873
- def list_tools(ctx: Any, mcp_ref: str) -> None:
874
- """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.
875
1054
 
876
1055
  Args:
877
1056
  ctx: Click context containing output format preferences
878
- 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
879
1060
 
880
1061
  Raises:
881
1062
  ClickException: If MCP not found or tools fetch fails
882
- """
883
- try:
884
- client = get_client(ctx)
885
1063
 
886
- # Resolve MCP using helper function
887
- mcp = _resolve_mcp(ctx, client, mcp_ref)
1064
+ Examples:
1065
+ Get tools from saved MCP:
1066
+ aip mcps tools <MCP_ID>
888
1067
 
889
- # Get tools from MCP
890
- with spinner_context(
891
- ctx,
892
- "[bold blue]Fetching MCP tools…[/bold blue]",
893
- console_override=console,
894
- ):
895
- tools = client.mcps.get_mcp_tools(mcp.id)
1068
+ Get tools from config file (without saving to DB):
1069
+ aip mcps tools --from-config mcp-config.json
896
1070
 
897
- # Define table columns: (data_key, header, style, width)
898
- columns = [
899
- ("name", "Name", ACCENT_STYLE, None),
900
- ("description", "Description", INFO, 50),
901
- ]
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)
902
1077
 
903
- # Transform function for safe dictionary access
904
- def transform_tool(tool: dict[str, Any]) -> dict[str, Any]:
905
- return {
906
- "name": tool.get("name", "N/A"),
907
- "description": tool.get("description", "N/A")[:47] + "..."
908
- if len(tool.get("description", "")) > 47
909
- else tool.get("description", "N/A"),
910
- }
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)
911
1082
 
912
- output_list(
913
- ctx,
914
- tools,
915
- f"{ICON_TOOL} Tools from MCP: {mcp.name}",
916
- columns,
917
- transform_tool,
918
- )
1083
+ if names_only:
1084
+ _output_tool_names(ctx, tools)
1085
+ else:
1086
+ _output_tools_table(ctx, tools, title)
919
1087
 
920
1088
  except Exception as e:
921
1089
  raise click.ClickException(str(e)) from e
@@ -1050,7 +1218,7 @@ def update(
1050
1218
  import_file: str | None,
1051
1219
  y: bool,
1052
1220
  ) -> None:
1053
- """Update an existing MCP with new configuration values.
1221
+ r"""Update an existing MCP with new configuration values.
1054
1222
 
1055
1223
  You can update an MCP by providing individual fields via CLI options, or by
1056
1224
  importing from a file and optionally overriding specific fields.
@@ -1075,6 +1243,7 @@ def update(
1075
1243
  CLI options override imported values when both are specified.
1076
1244
  Uses PATCH for import-based updates, PUT/PATCH for CLI-only updates.
1077
1245
 
1246
+ \b
1078
1247
  Examples:
1079
1248
  Update with CLI options:
1080
1249
  aip mcps update my-mcp --name new-name --transport sse
@@ -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)
@@ -50,6 +48,14 @@ def list_models(ctx: Any) -> None:
50
48
 
51
49
  # Transform function for safe dictionary access
52
50
  def transform_model(model: dict[str, Any]) -> dict[str, Any]:
51
+ """Transform a model dictionary to a display row dictionary.
52
+
53
+ Args:
54
+ model: Model dictionary to transform.
55
+
56
+ Returns:
57
+ Dictionary with id, provider, name, and base_url fields.
58
+ """
53
59
  return {
54
60
  "id": str(model.get("id", "N/A")),
55
61
  "provider": model.get("provider", "N/A"),