glaip-sdk 0.1.0__py3-none-any.whl → 0.6.10__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.
- glaip_sdk/__init__.py +5 -2
- glaip_sdk/_version.py +10 -3
- glaip_sdk/agents/__init__.py +27 -0
- glaip_sdk/agents/base.py +1191 -0
- glaip_sdk/branding.py +15 -6
- glaip_sdk/cli/account_store.py +540 -0
- glaip_sdk/cli/agent_config.py +2 -6
- glaip_sdk/cli/auth.py +265 -45
- glaip_sdk/cli/commands/__init__.py +2 -2
- glaip_sdk/cli/commands/accounts.py +746 -0
- glaip_sdk/cli/commands/agents.py +251 -173
- glaip_sdk/cli/commands/common_config.py +101 -0
- glaip_sdk/cli/commands/configure.py +735 -143
- glaip_sdk/cli/commands/mcps.py +266 -134
- glaip_sdk/cli/commands/models.py +13 -9
- glaip_sdk/cli/commands/tools.py +67 -88
- glaip_sdk/cli/commands/transcripts.py +755 -0
- glaip_sdk/cli/commands/update.py +3 -8
- glaip_sdk/cli/config.py +49 -7
- glaip_sdk/cli/constants.py +38 -0
- glaip_sdk/cli/context.py +8 -0
- glaip_sdk/cli/core/__init__.py +79 -0
- glaip_sdk/cli/core/context.py +124 -0
- glaip_sdk/cli/core/output.py +846 -0
- glaip_sdk/cli/core/prompting.py +649 -0
- glaip_sdk/cli/core/rendering.py +187 -0
- glaip_sdk/cli/display.py +45 -32
- glaip_sdk/cli/hints.py +57 -0
- glaip_sdk/cli/io.py +14 -17
- glaip_sdk/cli/main.py +232 -143
- glaip_sdk/cli/masking.py +21 -33
- glaip_sdk/cli/mcp_validators.py +5 -15
- glaip_sdk/cli/pager.py +12 -19
- glaip_sdk/cli/parsers/__init__.py +1 -3
- glaip_sdk/cli/parsers/json_input.py +11 -22
- glaip_sdk/cli/resolution.py +3 -9
- glaip_sdk/cli/rich_helpers.py +1 -3
- glaip_sdk/cli/slash/__init__.py +0 -9
- glaip_sdk/cli/slash/accounts_controller.py +578 -0
- glaip_sdk/cli/slash/accounts_shared.py +75 -0
- glaip_sdk/cli/slash/agent_session.py +65 -29
- glaip_sdk/cli/slash/prompt.py +24 -10
- glaip_sdk/cli/slash/remote_runs_controller.py +566 -0
- glaip_sdk/cli/slash/session.py +807 -225
- glaip_sdk/cli/slash/tui/__init__.py +9 -0
- glaip_sdk/cli/slash/tui/accounts.tcss +86 -0
- glaip_sdk/cli/slash/tui/accounts_app.py +876 -0
- glaip_sdk/cli/slash/tui/background_tasks.py +72 -0
- glaip_sdk/cli/slash/tui/loading.py +58 -0
- glaip_sdk/cli/slash/tui/remote_runs_app.py +628 -0
- glaip_sdk/cli/transcript/__init__.py +12 -52
- glaip_sdk/cli/transcript/cache.py +258 -60
- glaip_sdk/cli/transcript/capture.py +72 -21
- glaip_sdk/cli/transcript/history.py +815 -0
- glaip_sdk/cli/transcript/launcher.py +1 -3
- glaip_sdk/cli/transcript/viewer.py +79 -499
- glaip_sdk/cli/update_notifier.py +177 -24
- glaip_sdk/cli/utils.py +242 -1308
- glaip_sdk/cli/validators.py +16 -18
- glaip_sdk/client/__init__.py +2 -1
- glaip_sdk/client/_agent_payloads.py +53 -37
- glaip_sdk/client/agent_runs.py +147 -0
- glaip_sdk/client/agents.py +320 -92
- glaip_sdk/client/base.py +78 -35
- glaip_sdk/client/main.py +19 -10
- glaip_sdk/client/mcps.py +123 -15
- glaip_sdk/client/run_rendering.py +136 -101
- glaip_sdk/client/shared.py +21 -0
- glaip_sdk/client/tools.py +163 -34
- glaip_sdk/client/validators.py +20 -48
- glaip_sdk/config/constants.py +11 -0
- glaip_sdk/exceptions.py +1 -3
- glaip_sdk/mcps/__init__.py +21 -0
- glaip_sdk/mcps/base.py +345 -0
- glaip_sdk/models/__init__.py +90 -0
- glaip_sdk/models/agent.py +47 -0
- glaip_sdk/models/agent_runs.py +116 -0
- glaip_sdk/models/common.py +42 -0
- glaip_sdk/models/mcp.py +33 -0
- glaip_sdk/models/tool.py +33 -0
- glaip_sdk/payload_schemas/__init__.py +1 -13
- glaip_sdk/payload_schemas/agent.py +1 -3
- glaip_sdk/registry/__init__.py +55 -0
- glaip_sdk/registry/agent.py +164 -0
- glaip_sdk/registry/base.py +139 -0
- glaip_sdk/registry/mcp.py +253 -0
- glaip_sdk/registry/tool.py +232 -0
- glaip_sdk/rich_components.py +58 -2
- glaip_sdk/runner/__init__.py +59 -0
- glaip_sdk/runner/base.py +84 -0
- glaip_sdk/runner/deps.py +115 -0
- glaip_sdk/runner/langgraph.py +706 -0
- glaip_sdk/runner/mcp_adapter/__init__.py +13 -0
- glaip_sdk/runner/mcp_adapter/base_mcp_adapter.py +43 -0
- glaip_sdk/runner/mcp_adapter/langchain_mcp_adapter.py +257 -0
- glaip_sdk/runner/mcp_adapter/mcp_config_builder.py +95 -0
- glaip_sdk/runner/tool_adapter/__init__.py +18 -0
- glaip_sdk/runner/tool_adapter/base_tool_adapter.py +44 -0
- glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +219 -0
- glaip_sdk/tools/__init__.py +22 -0
- glaip_sdk/tools/base.py +435 -0
- glaip_sdk/utils/__init__.py +58 -12
- glaip_sdk/utils/a2a/__init__.py +34 -0
- glaip_sdk/utils/a2a/event_processor.py +188 -0
- glaip_sdk/utils/agent_config.py +4 -14
- glaip_sdk/utils/bundler.py +267 -0
- glaip_sdk/utils/client.py +111 -0
- glaip_sdk/utils/client_utils.py +46 -28
- glaip_sdk/utils/datetime_helpers.py +58 -0
- glaip_sdk/utils/discovery.py +78 -0
- glaip_sdk/utils/display.py +25 -21
- glaip_sdk/utils/export.py +143 -0
- glaip_sdk/utils/general.py +1 -36
- glaip_sdk/utils/import_export.py +15 -16
- glaip_sdk/utils/import_resolver.py +492 -0
- glaip_sdk/utils/instructions.py +101 -0
- glaip_sdk/utils/rendering/__init__.py +115 -1
- glaip_sdk/utils/rendering/formatting.py +7 -35
- glaip_sdk/utils/rendering/layout/__init__.py +64 -0
- glaip_sdk/utils/rendering/{renderer → layout}/panels.py +10 -3
- glaip_sdk/utils/rendering/{renderer → layout}/progress.py +73 -12
- glaip_sdk/utils/rendering/layout/summary.py +74 -0
- glaip_sdk/utils/rendering/layout/transcript.py +606 -0
- glaip_sdk/utils/rendering/models.py +3 -6
- glaip_sdk/utils/rendering/renderer/__init__.py +9 -49
- glaip_sdk/utils/rendering/renderer/base.py +258 -1577
- glaip_sdk/utils/rendering/renderer/config.py +1 -5
- glaip_sdk/utils/rendering/renderer/debug.py +30 -34
- glaip_sdk/utils/rendering/renderer/factory.py +138 -0
- glaip_sdk/utils/rendering/renderer/stream.py +10 -51
- glaip_sdk/utils/rendering/renderer/summary_window.py +79 -0
- glaip_sdk/utils/rendering/renderer/thinking.py +273 -0
- glaip_sdk/utils/rendering/renderer/toggle.py +1 -3
- glaip_sdk/utils/rendering/renderer/tool_panels.py +442 -0
- glaip_sdk/utils/rendering/renderer/transcript_mode.py +162 -0
- glaip_sdk/utils/rendering/state.py +204 -0
- glaip_sdk/utils/rendering/step_tree_state.py +1 -3
- glaip_sdk/utils/rendering/steps/__init__.py +34 -0
- glaip_sdk/utils/rendering/{steps.py → steps/event_processor.py} +76 -517
- glaip_sdk/utils/rendering/steps/format.py +176 -0
- glaip_sdk/utils/rendering/steps/manager.py +387 -0
- glaip_sdk/utils/rendering/timing.py +36 -0
- glaip_sdk/utils/rendering/viewer/__init__.py +21 -0
- glaip_sdk/utils/rendering/viewer/presenter.py +184 -0
- glaip_sdk/utils/resource_refs.py +29 -26
- glaip_sdk/utils/runtime_config.py +425 -0
- glaip_sdk/utils/serialization.py +32 -46
- glaip_sdk/utils/sync.py +142 -0
- glaip_sdk/utils/tool_detection.py +33 -0
- glaip_sdk/utils/validation.py +20 -28
- {glaip_sdk-0.1.0.dist-info → glaip_sdk-0.6.10.dist-info}/METADATA +42 -4
- glaip_sdk-0.6.10.dist-info/RECORD +159 -0
- {glaip_sdk-0.1.0.dist-info → glaip_sdk-0.6.10.dist-info}/WHEEL +1 -1
- glaip_sdk/models.py +0 -259
- glaip_sdk-0.1.0.dist-info/RECORD +0 -82
- {glaip_sdk-0.1.0.dist-info → glaip_sdk-0.6.10.dist-info}/entry_points.txt +0 -0
glaip_sdk/cli/commands/mcps.py
CHANGED
|
@@ -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:
|
|
@@ -76,9 +79,7 @@ def _is_sensitive_data(val: Any) -> bool:
|
|
|
76
79
|
return False
|
|
77
80
|
|
|
78
81
|
sensitive_patterns = {"token", "password", "secret", "key", "credential"}
|
|
79
|
-
return any(
|
|
80
|
-
pattern in str(k).lower() for k in val.keys() for pattern in sensitive_patterns
|
|
81
|
-
)
|
|
82
|
+
return any(pattern in str(k).lower() for k in val.keys() for pattern in sensitive_patterns)
|
|
82
83
|
|
|
83
84
|
|
|
84
85
|
def _redact_sensitive_dict(val: dict[str, Any]) -> dict[str, Any]:
|
|
@@ -143,13 +144,8 @@ def _build_empty_override_warnings(empty_fields: list[str]) -> list[str]:
|
|
|
143
144
|
if not empty_fields:
|
|
144
145
|
return []
|
|
145
146
|
|
|
146
|
-
warnings = [
|
|
147
|
-
|
|
148
|
-
]
|
|
149
|
-
warnings.extend(
|
|
150
|
-
f"- [yellow]{field}: will be set to empty string[/yellow]"
|
|
151
|
-
for field in empty_fields
|
|
152
|
-
)
|
|
147
|
+
warnings = ["\n[yellow]⚠️ Warning: Empty values provided via CLI will override import values[/yellow]"]
|
|
148
|
+
warnings.extend(f"- [yellow]{field}: will be set to empty string[/yellow]" for field in empty_fields)
|
|
153
149
|
return warnings
|
|
154
150
|
|
|
155
151
|
|
|
@@ -250,9 +246,7 @@ def _build_update_data_from_sources(
|
|
|
250
246
|
)
|
|
251
247
|
if auth is not None:
|
|
252
248
|
parsed_auth = parse_json_input(auth)
|
|
253
|
-
update_data["authentication"] = validate_mcp_auth_structure(
|
|
254
|
-
parsed_auth, source="--auth"
|
|
255
|
-
)
|
|
249
|
+
update_data["authentication"] = validate_mcp_auth_structure(parsed_auth, source="--auth")
|
|
256
250
|
|
|
257
251
|
return update_data
|
|
258
252
|
|
|
@@ -308,30 +302,37 @@ def mcps_group() -> None:
|
|
|
308
302
|
pass
|
|
309
303
|
|
|
310
304
|
|
|
311
|
-
def _resolve_mcp(
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
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.
|
|
315
311
|
|
|
316
312
|
Args:
|
|
317
|
-
ctx: Click context
|
|
318
|
-
client: API client
|
|
319
|
-
ref: MCP
|
|
320
|
-
select:
|
|
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).
|
|
321
317
|
|
|
322
318
|
Returns:
|
|
323
|
-
MCP
|
|
319
|
+
MCP instance if resolution succeeds, None if not found.
|
|
324
320
|
|
|
325
321
|
Raises:
|
|
326
|
-
ClickException:
|
|
322
|
+
click.ClickException: When resolution fails or selection is invalid.
|
|
327
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
|
|
328
329
|
return resolve_resource_reference(
|
|
329
330
|
ctx,
|
|
330
331
|
client,
|
|
331
332
|
ref,
|
|
332
333
|
"mcp",
|
|
333
|
-
|
|
334
|
-
|
|
334
|
+
get_by_id_func,
|
|
335
|
+
find_by_name_func,
|
|
335
336
|
"MCP",
|
|
336
337
|
select=select,
|
|
337
338
|
)
|
|
@@ -524,12 +525,11 @@ def list_mcps(ctx: Any) -> None:
|
|
|
524
525
|
ClickException: If API request fails
|
|
525
526
|
"""
|
|
526
527
|
try:
|
|
527
|
-
|
|
528
|
-
with spinner_context(
|
|
528
|
+
with with_client_and_spinner(
|
|
529
529
|
ctx,
|
|
530
530
|
"[bold blue]Fetching MCPs…[/bold blue]",
|
|
531
531
|
console_override=console,
|
|
532
|
-
):
|
|
532
|
+
) as client:
|
|
533
533
|
mcps = client.mcps.list_mcps()
|
|
534
534
|
|
|
535
535
|
# Define table columns: (data_key, header, style, width)
|
|
@@ -541,22 +541,26 @@ def list_mcps(ctx: Any) -> None:
|
|
|
541
541
|
|
|
542
542
|
# Transform function for safe dictionary access
|
|
543
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
|
+
"""
|
|
544
552
|
row = coerce_to_row(mcp, ["id", "name", "config"])
|
|
545
553
|
# Ensure id is always a string
|
|
546
554
|
row["id"] = str(row["id"])
|
|
547
555
|
# Truncate config field for display
|
|
548
556
|
if row["config"] != "N/A":
|
|
549
|
-
row["config"] = (
|
|
550
|
-
str(row["config"])[:50] + "..."
|
|
551
|
-
if len(str(row["config"])) > 50
|
|
552
|
-
else str(row["config"])
|
|
553
|
-
)
|
|
557
|
+
row["config"] = str(row["config"])[:50] + "..." if len(str(row["config"])) > 50 else str(row["config"])
|
|
554
558
|
return row
|
|
555
559
|
|
|
556
560
|
output_list(ctx, mcps, "🔌 Available MCPs", columns, transform_mcp)
|
|
557
561
|
|
|
558
562
|
except Exception as e:
|
|
559
|
-
raise click.ClickException(str(e))
|
|
563
|
+
raise click.ClickException(str(e)) from e
|
|
560
564
|
|
|
561
565
|
|
|
562
566
|
@mcps_group.command()
|
|
@@ -590,7 +594,7 @@ def create(
|
|
|
590
594
|
auth: str | None,
|
|
591
595
|
import_file: str | None,
|
|
592
596
|
) -> None:
|
|
593
|
-
"""Create a new MCP with specified configuration.
|
|
597
|
+
r"""Create a new MCP with specified configuration.
|
|
594
598
|
|
|
595
599
|
You can create an MCP by providing all parameters via CLI options, or by
|
|
596
600
|
importing from a file and optionally overriding specific fields.
|
|
@@ -608,6 +612,7 @@ def create(
|
|
|
608
612
|
Raises:
|
|
609
613
|
ClickException: If JSON parsing fails or API request fails
|
|
610
614
|
|
|
615
|
+
\b
|
|
611
616
|
Examples:
|
|
612
617
|
Create from CLI options:
|
|
613
618
|
aip mcps create --name my-mcp --transport http --config '{"url": "https://api.example.com"}'
|
|
@@ -619,11 +624,11 @@ def create(
|
|
|
619
624
|
aip mcps create --import mcp-export.json --name new-name --transport sse
|
|
620
625
|
"""
|
|
621
626
|
try:
|
|
622
|
-
client
|
|
627
|
+
# Get API client instance for MCP operations
|
|
628
|
+
api_client = get_client(ctx)
|
|
623
629
|
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
)
|
|
630
|
+
# Process import file if specified, otherwise use None
|
|
631
|
+
import_payload = _load_import_ready_payload(import_file) if import_file is not None else None
|
|
627
632
|
|
|
628
633
|
merged_payload, missing_fields = _merge_import_payload(
|
|
629
634
|
import_payload,
|
|
@@ -636,8 +641,7 @@ def create(
|
|
|
636
641
|
|
|
637
642
|
if missing_fields:
|
|
638
643
|
raise click.ClickException(
|
|
639
|
-
"Missing required fields after combining import and CLI values: "
|
|
640
|
-
+ ", ".join(missing_fields)
|
|
644
|
+
"Missing required fields after combining import and CLI values: " + ", ".join(missing_fields)
|
|
641
645
|
)
|
|
642
646
|
|
|
643
647
|
effective_name = merged_payload["name"]
|
|
@@ -667,7 +671,7 @@ def create(
|
|
|
667
671
|
if mcp_metadata is not None:
|
|
668
672
|
create_kwargs["mcp_metadata"] = mcp_metadata
|
|
669
673
|
|
|
670
|
-
mcp =
|
|
674
|
+
mcp = api_client.mcps.create_mcp(**create_kwargs)
|
|
671
675
|
|
|
672
676
|
# Handle JSON output
|
|
673
677
|
handle_json_output(ctx, mcp.model_dump())
|
|
@@ -714,21 +718,14 @@ def _handle_mcp_export(
|
|
|
714
718
|
detected_format = detect_export_format(export_path)
|
|
715
719
|
|
|
716
720
|
# Always export comprehensive data - re-fetch with full details
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
print_markup(
|
|
726
|
-
f"[{WARNING_STYLE}]⚠️ Could not fetch full MCP details: {e}[/]",
|
|
727
|
-
console=console,
|
|
728
|
-
)
|
|
729
|
-
print_markup(
|
|
730
|
-
f"[{WARNING_STYLE}]⚠️ Proceeding with available data[/]", console=console
|
|
731
|
-
)
|
|
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
|
+
)
|
|
732
729
|
|
|
733
730
|
# Determine if we should prompt for secrets
|
|
734
731
|
prompt_for_secrets = not no_auth_prompt and sys.stdin.isatty()
|
|
@@ -736,8 +733,7 @@ def _handle_mcp_export(
|
|
|
736
733
|
# Warn user if non-interactive mode forces placeholder usage
|
|
737
734
|
if not no_auth_prompt and not sys.stdin.isatty():
|
|
738
735
|
print_markup(
|
|
739
|
-
f"[{WARNING_STYLE}]⚠️ Non-interactive mode detected. "
|
|
740
|
-
"Using placeholder values for secrets.[/]",
|
|
736
|
+
f"[{WARNING_STYLE}]⚠️ Non-interactive mode detected. Using placeholder values for secrets.[/]",
|
|
741
737
|
console=console,
|
|
742
738
|
)
|
|
743
739
|
|
|
@@ -772,8 +768,7 @@ def _handle_mcp_export(
|
|
|
772
768
|
write_resource_export(export_path, export_payload, detected_format)
|
|
773
769
|
|
|
774
770
|
print_markup(
|
|
775
|
-
f"[{SUCCESS_STYLE}]✅ Complete MCP configuration exported to: "
|
|
776
|
-
f"{export_path} (format: {detected_format})[/]",
|
|
771
|
+
f"[{SUCCESS_STYLE}]✅ Complete MCP configuration exported to: {export_path} (format: {detected_format})[/]",
|
|
777
772
|
console=console,
|
|
778
773
|
)
|
|
779
774
|
|
|
@@ -801,11 +796,7 @@ def _display_mcp_details(ctx: Any, client: Any, mcp: Any) -> None:
|
|
|
801
796
|
|
|
802
797
|
if raw_mcp_data:
|
|
803
798
|
# Use raw API data - this preserves ALL fields
|
|
804
|
-
formatted_data = raw_mcp_data
|
|
805
|
-
if "created_at" in formatted_data:
|
|
806
|
-
formatted_data["created_at"] = format_datetime(formatted_data["created_at"])
|
|
807
|
-
if "updated_at" in formatted_data:
|
|
808
|
-
formatted_data["updated_at"] = format_datetime(formatted_data["updated_at"])
|
|
799
|
+
formatted_data = format_datetime_fields(raw_mcp_data)
|
|
809
800
|
|
|
810
801
|
output_result(
|
|
811
802
|
ctx,
|
|
@@ -832,8 +823,7 @@ def _display_mcp_details(ctx: Any, client: Any, mcp: Any) -> None:
|
|
|
832
823
|
@click.option(
|
|
833
824
|
"--export",
|
|
834
825
|
type=click.Path(dir_okay=False, writable=True),
|
|
835
|
-
help="Export complete MCP configuration to file "
|
|
836
|
-
"(format auto-detected from .json/.yaml extension)",
|
|
826
|
+
help="Export complete MCP configuration to file (format auto-detected from .json/.yaml extension)",
|
|
837
827
|
)
|
|
838
828
|
@click.option(
|
|
839
829
|
"--no-auth-prompt",
|
|
@@ -855,7 +845,7 @@ def get(
|
|
|
855
845
|
no_auth_prompt: bool,
|
|
856
846
|
auth_placeholder: str,
|
|
857
847
|
) -> None:
|
|
858
|
-
"""Get MCP details and optionally export configuration to file.
|
|
848
|
+
r"""Get MCP details and optionally export configuration to file.
|
|
859
849
|
|
|
860
850
|
Args:
|
|
861
851
|
ctx: Click context containing output format preferences
|
|
@@ -867,6 +857,7 @@ def get(
|
|
|
867
857
|
Raises:
|
|
868
858
|
ClickException: If MCP not found or export fails
|
|
869
859
|
|
|
860
|
+
\b
|
|
870
861
|
Examples:
|
|
871
862
|
aip mcps get my-mcp
|
|
872
863
|
aip mcps get my-mcp --export mcp.json # Export as JSON
|
|
@@ -880,70 +871,222 @@ def get(
|
|
|
880
871
|
|
|
881
872
|
# Handle export option
|
|
882
873
|
if export:
|
|
883
|
-
_handle_mcp_export(
|
|
884
|
-
ctx, client, mcp, Path(export), no_auth_prompt, auth_placeholder
|
|
885
|
-
)
|
|
874
|
+
_handle_mcp_export(ctx, client, mcp, Path(export), no_auth_prompt, auth_placeholder)
|
|
886
875
|
|
|
887
876
|
# Display MCP details
|
|
888
877
|
_display_mcp_details(ctx, client, mcp)
|
|
889
878
|
|
|
890
879
|
except Exception as e:
|
|
891
|
-
raise click.ClickException(str(e))
|
|
880
|
+
raise click.ClickException(str(e)) from e
|
|
881
|
+
|
|
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
|
+
)
|
|
892
1034
|
|
|
893
1035
|
|
|
894
1036
|
@mcps_group.command("tools")
|
|
895
|
-
@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
|
+
)
|
|
896
1050
|
@output_flags()
|
|
897
1051
|
@click.pass_context
|
|
898
|
-
def list_tools(ctx: Any, mcp_ref: str) -> None:
|
|
899
|
-
"""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.
|
|
900
1054
|
|
|
901
1055
|
Args:
|
|
902
1056
|
ctx: Click context containing output format preferences
|
|
903
|
-
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
|
|
904
1060
|
|
|
905
1061
|
Raises:
|
|
906
1062
|
ClickException: If MCP not found or tools fetch fails
|
|
907
|
-
"""
|
|
908
|
-
try:
|
|
909
|
-
client = get_client(ctx)
|
|
910
1063
|
|
|
911
|
-
|
|
912
|
-
|
|
1064
|
+
Examples:
|
|
1065
|
+
Get tools from saved MCP:
|
|
1066
|
+
aip mcps tools <MCP_ID>
|
|
913
1067
|
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
ctx,
|
|
917
|
-
"[bold blue]Fetching MCP tools…[/bold blue]",
|
|
918
|
-
console_override=console,
|
|
919
|
-
):
|
|
920
|
-
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
|
|
921
1070
|
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
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)
|
|
927
1077
|
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
"description": tool.get("description", "N/A")[:47] + "..."
|
|
933
|
-
if len(tool.get("description", "")) > 47
|
|
934
|
-
else tool.get("description", "N/A"),
|
|
935
|
-
}
|
|
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)
|
|
936
1082
|
|
|
937
|
-
|
|
938
|
-
ctx,
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
columns,
|
|
942
|
-
transform_tool,
|
|
943
|
-
)
|
|
1083
|
+
if names_only:
|
|
1084
|
+
_output_tool_names(ctx, tools)
|
|
1085
|
+
else:
|
|
1086
|
+
_output_tools_table(ctx, tools, title)
|
|
944
1087
|
|
|
945
1088
|
except Exception as e:
|
|
946
|
-
raise click.ClickException(str(e))
|
|
1089
|
+
raise click.ClickException(str(e)) from e
|
|
947
1090
|
|
|
948
1091
|
|
|
949
1092
|
@mcps_group.command("connect")
|
|
@@ -996,20 +1139,17 @@ def connect(ctx: Any, config_file: str) -> None:
|
|
|
996
1139
|
handle_json_output(ctx, result)
|
|
997
1140
|
else:
|
|
998
1141
|
success_panel = AIPPanel(
|
|
999
|
-
f"[{SUCCESS_STYLE}]✓[/] MCP connection successful!\n\n"
|
|
1000
|
-
f"[bold]Result:[/bold] {result}",
|
|
1142
|
+
f"[{SUCCESS_STYLE}]✓[/] MCP connection successful!\n\n[bold]Result:[/bold] {result}",
|
|
1001
1143
|
title="🔌 Connection",
|
|
1002
1144
|
border_style=SUCCESS,
|
|
1003
1145
|
)
|
|
1004
1146
|
console.print(success_panel)
|
|
1005
1147
|
|
|
1006
1148
|
except Exception as e:
|
|
1007
|
-
raise click.ClickException(str(e))
|
|
1149
|
+
raise click.ClickException(str(e)) from e
|
|
1008
1150
|
|
|
1009
1151
|
|
|
1010
|
-
def _generate_update_preview(
|
|
1011
|
-
mcp: Any, update_data: dict[str, Any], cli_overrides: dict[str, Any]
|
|
1012
|
-
) -> str:
|
|
1152
|
+
def _generate_update_preview(mcp: Any, update_data: dict[str, Any], cli_overrides: dict[str, Any]) -> str:
|
|
1013
1153
|
"""Generate formatted preview of changes for user confirmation.
|
|
1014
1154
|
|
|
1015
1155
|
Args:
|
|
@@ -1020,9 +1160,7 @@ def _generate_update_preview(
|
|
|
1020
1160
|
Returns:
|
|
1021
1161
|
Formatted preview string showing old→new values
|
|
1022
1162
|
"""
|
|
1023
|
-
lines = [
|
|
1024
|
-
f"\n[bold]The following fields will be updated for MCP '{mcp.name}':[/bold]\n"
|
|
1025
|
-
]
|
|
1163
|
+
lines = [f"\n[bold]The following fields will be updated for MCP '{mcp.name}':[/bold]\n"]
|
|
1026
1164
|
|
|
1027
1165
|
empty_overrides = []
|
|
1028
1166
|
|
|
@@ -1048,9 +1186,7 @@ def _generate_update_preview(
|
|
|
1048
1186
|
@mcps_group.command()
|
|
1049
1187
|
@click.argument("mcp_ref")
|
|
1050
1188
|
@click.option("--name", help="New MCP name")
|
|
1051
|
-
@click.option(
|
|
1052
|
-
"--transport", type=click.Choice(["http", "sse"]), help="New transport protocol"
|
|
1053
|
-
)
|
|
1189
|
+
@click.option("--transport", type=click.Choice(["http", "sse"]), help="New transport protocol")
|
|
1054
1190
|
@click.option("--description", help="New description")
|
|
1055
1191
|
@click.option(
|
|
1056
1192
|
"--config",
|
|
@@ -1082,7 +1218,7 @@ def update(
|
|
|
1082
1218
|
import_file: str | None,
|
|
1083
1219
|
y: bool,
|
|
1084
1220
|
) -> None:
|
|
1085
|
-
"""Update an existing MCP with new configuration values.
|
|
1221
|
+
r"""Update an existing MCP with new configuration values.
|
|
1086
1222
|
|
|
1087
1223
|
You can update an MCP by providing individual fields via CLI options, or by
|
|
1088
1224
|
importing from a file and optionally overriding specific fields.
|
|
@@ -1107,6 +1243,7 @@ def update(
|
|
|
1107
1243
|
CLI options override imported values when both are specified.
|
|
1108
1244
|
Uses PATCH for import-based updates, PUT/PATCH for CLI-only updates.
|
|
1109
1245
|
|
|
1246
|
+
\b
|
|
1110
1247
|
Examples:
|
|
1111
1248
|
Update with CLI options:
|
|
1112
1249
|
aip mcps update my-mcp --name new-name --transport sse
|
|
@@ -1121,12 +1258,11 @@ def update(
|
|
|
1121
1258
|
client = get_client(ctx)
|
|
1122
1259
|
|
|
1123
1260
|
# Validate that at least one update method is provided
|
|
1124
|
-
cli_flags_provided = any(
|
|
1125
|
-
v is not None for v in [name, transport, description, config, auth]
|
|
1126
|
-
)
|
|
1261
|
+
cli_flags_provided = any(v is not None for v in [name, transport, description, config, auth])
|
|
1127
1262
|
if not import_file and not cli_flags_provided:
|
|
1128
1263
|
raise click.ClickException(
|
|
1129
|
-
"No update fields specified. Use --import or one of:
|
|
1264
|
+
"No update fields specified. Use --import or one of: "
|
|
1265
|
+
"--name, --transport, --description, --config, --auth"
|
|
1130
1266
|
)
|
|
1131
1267
|
|
|
1132
1268
|
# Resolve MCP using helper function
|
|
@@ -1140,18 +1276,14 @@ def update(
|
|
|
1140
1276
|
return
|
|
1141
1277
|
|
|
1142
1278
|
# Build update data from import and CLI flags
|
|
1143
|
-
update_data = _build_update_data_from_sources(
|
|
1144
|
-
import_payload, mcp, name, transport, description, config, auth
|
|
1145
|
-
)
|
|
1279
|
+
update_data = _build_update_data_from_sources(import_payload, mcp, name, transport, description, config, auth)
|
|
1146
1280
|
|
|
1147
1281
|
if not update_data:
|
|
1148
1282
|
raise click.ClickException("No update fields specified")
|
|
1149
1283
|
|
|
1150
1284
|
# Show confirmation preview for import-based updates (unless -y flag)
|
|
1151
1285
|
if import_payload and not y:
|
|
1152
|
-
cli_overrides = _collect_cli_overrides(
|
|
1153
|
-
name, transport, description, config, auth
|
|
1154
|
-
)
|
|
1286
|
+
cli_overrides = _collect_cli_overrides(name, transport, description, config, auth)
|
|
1155
1287
|
preview = _generate_update_preview(mcp, update_data, cli_overrides)
|
|
1156
1288
|
print_markup(preview)
|
|
1157
1289
|
|