glaip-sdk 0.0.18__py3-none-any.whl → 0.0.20__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/_version.py +2 -2
- glaip_sdk/branding.py +27 -2
- glaip_sdk/cli/auth.py +93 -28
- glaip_sdk/cli/commands/__init__.py +2 -2
- glaip_sdk/cli/commands/agents.py +108 -21
- glaip_sdk/cli/commands/configure.py +141 -90
- glaip_sdk/cli/commands/mcps.py +371 -48
- glaip_sdk/cli/commands/models.py +4 -3
- glaip_sdk/cli/commands/tools.py +27 -14
- glaip_sdk/cli/commands/update.py +66 -0
- glaip_sdk/cli/config.py +13 -2
- glaip_sdk/cli/display.py +35 -26
- glaip_sdk/cli/io.py +14 -5
- glaip_sdk/cli/main.py +185 -73
- glaip_sdk/cli/pager.py +2 -1
- glaip_sdk/cli/parsers/json_input.py +62 -14
- glaip_sdk/cli/resolution.py +4 -1
- glaip_sdk/cli/slash/__init__.py +3 -4
- glaip_sdk/cli/slash/agent_session.py +88 -36
- glaip_sdk/cli/slash/prompt.py +20 -48
- glaip_sdk/cli/slash/session.py +440 -189
- glaip_sdk/cli/transcript/__init__.py +71 -0
- glaip_sdk/cli/transcript/cache.py +338 -0
- glaip_sdk/cli/transcript/capture.py +278 -0
- glaip_sdk/cli/transcript/export.py +38 -0
- glaip_sdk/cli/transcript/launcher.py +79 -0
- glaip_sdk/cli/transcript/viewer.py +624 -0
- glaip_sdk/cli/update_notifier.py +29 -5
- glaip_sdk/cli/utils.py +256 -74
- glaip_sdk/client/agents.py +3 -1
- glaip_sdk/client/run_rendering.py +2 -2
- glaip_sdk/icons.py +19 -0
- glaip_sdk/models.py +6 -0
- glaip_sdk/rich_components.py +29 -1
- glaip_sdk/utils/__init__.py +1 -1
- glaip_sdk/utils/client_utils.py +6 -4
- glaip_sdk/utils/display.py +61 -32
- glaip_sdk/utils/rendering/formatting.py +6 -5
- glaip_sdk/utils/rendering/renderer/base.py +213 -66
- glaip_sdk/utils/rendering/renderer/debug.py +73 -16
- glaip_sdk/utils/rendering/renderer/panels.py +27 -15
- glaip_sdk/utils/rendering/renderer/progress.py +61 -38
- glaip_sdk/utils/serialization.py +5 -2
- glaip_sdk/utils/validation.py +1 -2
- {glaip_sdk-0.0.18.dist-info → glaip_sdk-0.0.20.dist-info}/METADATA +1 -1
- glaip_sdk-0.0.20.dist-info/RECORD +80 -0
- glaip_sdk/utils/rich_utils.py +0 -29
- glaip_sdk-0.0.18.dist-info/RECORD +0 -73
- {glaip_sdk-0.0.18.dist-info → glaip_sdk-0.0.20.dist-info}/WHEEL +0 -0
- {glaip_sdk-0.0.18.dist-info → glaip_sdk-0.0.20.dist-info}/entry_points.txt +0 -0
glaip_sdk/cli/commands/mcps.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Authors:
|
|
4
4
|
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
5
|
+
Putu Ravindra Wiguna (putu.r.wiguna@gdplabs.id)
|
|
5
6
|
"""
|
|
6
7
|
|
|
7
8
|
import json
|
|
@@ -12,6 +13,13 @@ from typing import Any
|
|
|
12
13
|
import click
|
|
13
14
|
from rich.console import Console
|
|
14
15
|
|
|
16
|
+
from glaip_sdk.branding import (
|
|
17
|
+
ACCENT_STYLE,
|
|
18
|
+
INFO,
|
|
19
|
+
SUCCESS,
|
|
20
|
+
SUCCESS_STYLE,
|
|
21
|
+
WARNING_STYLE,
|
|
22
|
+
)
|
|
15
23
|
from glaip_sdk.cli.context import detect_export_format, get_ctx_value, output_flags
|
|
16
24
|
from glaip_sdk.cli.display import (
|
|
17
25
|
display_api_error,
|
|
@@ -43,6 +51,7 @@ from glaip_sdk.cli.utils import (
|
|
|
43
51
|
from glaip_sdk.config.constants import (
|
|
44
52
|
DEFAULT_MCP_TYPE,
|
|
45
53
|
)
|
|
54
|
+
from glaip_sdk.icons import ICON_TOOL
|
|
46
55
|
from glaip_sdk.rich_components import AIPPanel
|
|
47
56
|
from glaip_sdk.utils import format_datetime
|
|
48
57
|
from glaip_sdk.utils.import_export import convert_export_to_import_format
|
|
@@ -54,6 +63,241 @@ from glaip_sdk.utils.serialization import (
|
|
|
54
63
|
console = Console()
|
|
55
64
|
|
|
56
65
|
|
|
66
|
+
def _is_sensitive_data(val: Any) -> bool:
|
|
67
|
+
"""Check if value contains sensitive authentication data.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
val: Value to check for sensitive information
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
True if the value appears to contain sensitive data
|
|
74
|
+
"""
|
|
75
|
+
if not isinstance(val, dict):
|
|
76
|
+
return False
|
|
77
|
+
|
|
78
|
+
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
|
+
|
|
83
|
+
|
|
84
|
+
def _redact_sensitive_dict(val: dict[str, Any]) -> dict[str, Any]:
|
|
85
|
+
"""Redact sensitive fields from a dictionary.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
val: Dictionary to redact
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Redacted dictionary
|
|
92
|
+
"""
|
|
93
|
+
redacted = val.copy()
|
|
94
|
+
sensitive_patterns = {"token", "password", "secret", "key", "credential"}
|
|
95
|
+
for k in redacted.keys():
|
|
96
|
+
if any(pattern in k.lower() for pattern in sensitive_patterns):
|
|
97
|
+
redacted[k] = "<REDACTED>"
|
|
98
|
+
return redacted
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _format_dict_value(val: dict[str, Any]) -> str:
|
|
102
|
+
"""Format a dictionary value for display.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
val: Dictionary to format
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
Formatted string representation
|
|
109
|
+
"""
|
|
110
|
+
if _is_sensitive_data(val):
|
|
111
|
+
redacted = _redact_sensitive_dict(val)
|
|
112
|
+
return json.dumps(redacted, indent=2)
|
|
113
|
+
return json.dumps(val, indent=2)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _format_preview_value(val: Any) -> str:
|
|
117
|
+
"""Format a value for display in update preview with sensitive data redaction.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
val: Value to format
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
Formatted string representation of the value
|
|
124
|
+
"""
|
|
125
|
+
if val is None:
|
|
126
|
+
return "[dim]None[/dim]"
|
|
127
|
+
if isinstance(val, dict):
|
|
128
|
+
return _format_dict_value(val)
|
|
129
|
+
if isinstance(val, str):
|
|
130
|
+
return f'"{val}"' if val else '""'
|
|
131
|
+
return str(val)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _build_empty_override_warnings(empty_fields: list[str]) -> list[str]:
|
|
135
|
+
"""Build warning lines for empty CLI overrides.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
empty_fields: List of field names with empty string overrides
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
List of formatted warning lines
|
|
142
|
+
"""
|
|
143
|
+
if not empty_fields:
|
|
144
|
+
return []
|
|
145
|
+
|
|
146
|
+
warnings = [
|
|
147
|
+
"\n[yellow]⚠️ Warning: Empty values provided via CLI will override import values[/yellow]"
|
|
148
|
+
]
|
|
149
|
+
warnings.extend(
|
|
150
|
+
f"- [yellow]{field}: will be set to empty string[/yellow]"
|
|
151
|
+
for field in empty_fields
|
|
152
|
+
)
|
|
153
|
+
return warnings
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _validate_import_payload_fields(import_payload: dict[str, Any]) -> bool:
|
|
157
|
+
"""Validate that import payload contains updatable fields.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
import_payload: Import payload to validate
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
True if payload has updatable fields, False otherwise
|
|
164
|
+
"""
|
|
165
|
+
updatable_fields = {"name", "transport", "description", "config", "authentication"}
|
|
166
|
+
has_updatable = any(field in import_payload for field in updatable_fields)
|
|
167
|
+
|
|
168
|
+
if not has_updatable:
|
|
169
|
+
available_fields = set(import_payload.keys())
|
|
170
|
+
print_markup(
|
|
171
|
+
"[yellow]⚠️ No updatable fields found in import file.[/yellow]\n"
|
|
172
|
+
f"[dim]Found fields: {', '.join(sorted(available_fields))}[/dim]\n"
|
|
173
|
+
f"[dim]Updatable fields: {', '.join(sorted(updatable_fields))}[/dim]"
|
|
174
|
+
)
|
|
175
|
+
return has_updatable
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _get_config_transport(
|
|
179
|
+
transport: str | None,
|
|
180
|
+
import_payload: dict[str, Any] | None,
|
|
181
|
+
mcp: Any,
|
|
182
|
+
) -> str | None:
|
|
183
|
+
"""Get the transport value for config validation.
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
transport: CLI transport flag
|
|
187
|
+
import_payload: Optional import payload
|
|
188
|
+
mcp: Current MCP object
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
Transport value or None
|
|
192
|
+
"""
|
|
193
|
+
if import_payload:
|
|
194
|
+
return transport or import_payload.get("transport")
|
|
195
|
+
return transport or getattr(mcp, "transport", None)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _build_update_data_from_sources(
|
|
199
|
+
import_payload: dict[str, Any] | None,
|
|
200
|
+
mcp: Any,
|
|
201
|
+
name: str | None,
|
|
202
|
+
transport: str | None,
|
|
203
|
+
description: str | None,
|
|
204
|
+
config: str | None,
|
|
205
|
+
auth: str | None,
|
|
206
|
+
) -> dict[str, Any]:
|
|
207
|
+
"""Build update data from import payload and CLI flags.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
import_payload: Optional import payload
|
|
211
|
+
mcp: Current MCP object
|
|
212
|
+
name: CLI name flag
|
|
213
|
+
transport: CLI transport flag
|
|
214
|
+
description: CLI description flag
|
|
215
|
+
config: CLI config flag
|
|
216
|
+
auth: CLI auth flag
|
|
217
|
+
|
|
218
|
+
Returns:
|
|
219
|
+
Dictionary with update data
|
|
220
|
+
"""
|
|
221
|
+
update_data = {}
|
|
222
|
+
|
|
223
|
+
# Start with import data if available
|
|
224
|
+
if import_payload:
|
|
225
|
+
updatable_fields = [
|
|
226
|
+
"name",
|
|
227
|
+
"transport",
|
|
228
|
+
"description",
|
|
229
|
+
"config",
|
|
230
|
+
"authentication",
|
|
231
|
+
]
|
|
232
|
+
for field in updatable_fields:
|
|
233
|
+
if field in import_payload:
|
|
234
|
+
update_data[field] = import_payload[field]
|
|
235
|
+
|
|
236
|
+
# CLI flags override import values
|
|
237
|
+
if name is not None:
|
|
238
|
+
update_data["name"] = name
|
|
239
|
+
if transport is not None:
|
|
240
|
+
update_data["transport"] = transport
|
|
241
|
+
if description is not None:
|
|
242
|
+
update_data["description"] = description
|
|
243
|
+
if config is not None:
|
|
244
|
+
parsed_config = parse_json_input(config)
|
|
245
|
+
config_transport = _get_config_transport(transport, import_payload, mcp)
|
|
246
|
+
update_data["config"] = validate_mcp_config_structure(
|
|
247
|
+
parsed_config,
|
|
248
|
+
transport=config_transport,
|
|
249
|
+
source="--config",
|
|
250
|
+
)
|
|
251
|
+
if auth is not None:
|
|
252
|
+
parsed_auth = parse_json_input(auth)
|
|
253
|
+
update_data["authentication"] = validate_mcp_auth_structure(
|
|
254
|
+
parsed_auth, source="--auth"
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
return update_data
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def _collect_cli_overrides(
|
|
261
|
+
name: str | None,
|
|
262
|
+
transport: str | None,
|
|
263
|
+
description: str | None,
|
|
264
|
+
config: str | None,
|
|
265
|
+
auth: str | None,
|
|
266
|
+
) -> dict[str, Any]:
|
|
267
|
+
"""Collect CLI flags that were explicitly provided.
|
|
268
|
+
|
|
269
|
+
Args:
|
|
270
|
+
name: CLI name flag
|
|
271
|
+
transport: CLI transport flag
|
|
272
|
+
description: CLI description flag
|
|
273
|
+
config: CLI config flag
|
|
274
|
+
auth: CLI auth flag
|
|
275
|
+
|
|
276
|
+
Returns:
|
|
277
|
+
Dictionary of provided CLI overrides
|
|
278
|
+
"""
|
|
279
|
+
cli_overrides = {}
|
|
280
|
+
if name is not None:
|
|
281
|
+
cli_overrides["name"] = name
|
|
282
|
+
if transport is not None:
|
|
283
|
+
cli_overrides["transport"] = transport
|
|
284
|
+
if description is not None:
|
|
285
|
+
cli_overrides["description"] = description
|
|
286
|
+
if config is not None:
|
|
287
|
+
cli_overrides["config"] = config
|
|
288
|
+
if auth is not None:
|
|
289
|
+
cli_overrides["auth"] = auth
|
|
290
|
+
return cli_overrides
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def _handle_cli_error(ctx: Any, error: Exception, operation: str) -> None:
|
|
294
|
+
"""Render CLI error once and exit with non-zero status."""
|
|
295
|
+
handle_json_output(ctx, error=error)
|
|
296
|
+
if get_ctx_value(ctx, "view") != "json":
|
|
297
|
+
display_api_error(error, operation)
|
|
298
|
+
ctx.exit(1)
|
|
299
|
+
|
|
300
|
+
|
|
57
301
|
@click.group(name="mcps", no_args_is_help=True)
|
|
58
302
|
def mcps_group() -> None:
|
|
59
303
|
"""MCP management operations.
|
|
@@ -291,8 +535,8 @@ def list_mcps(ctx: Any) -> None:
|
|
|
291
535
|
# Define table columns: (data_key, header, style, width)
|
|
292
536
|
columns = [
|
|
293
537
|
("id", "ID", "dim", 36),
|
|
294
|
-
("name", "Name",
|
|
295
|
-
("config", "Config",
|
|
538
|
+
("name", "Name", ACCENT_STYLE, None),
|
|
539
|
+
("config", "Config", INFO, None),
|
|
296
540
|
]
|
|
297
541
|
|
|
298
542
|
# Transform function for safe dictionary access
|
|
@@ -440,10 +684,7 @@ def create(
|
|
|
440
684
|
handle_rich_output(ctx, rich_panel)
|
|
441
685
|
|
|
442
686
|
except Exception as e:
|
|
443
|
-
|
|
444
|
-
if get_ctx_value(ctx, "view") != "json":
|
|
445
|
-
display_api_error(e, "MCP creation")
|
|
446
|
-
raise click.ClickException(str(e))
|
|
687
|
+
_handle_cli_error(ctx, e, "MCP creation")
|
|
447
688
|
|
|
448
689
|
|
|
449
690
|
def _handle_mcp_export(
|
|
@@ -482,11 +723,11 @@ def _handle_mcp_export(
|
|
|
482
723
|
mcp = client.mcps.get_mcp_by_id(mcp.id)
|
|
483
724
|
except Exception as e:
|
|
484
725
|
print_markup(
|
|
485
|
-
f"[
|
|
726
|
+
f"[{WARNING_STYLE}]⚠️ Could not fetch full MCP details: {e}[/]",
|
|
486
727
|
console=console,
|
|
487
728
|
)
|
|
488
729
|
print_markup(
|
|
489
|
-
"[
|
|
730
|
+
f"[{WARNING_STYLE}]⚠️ Proceeding with available data[/]", console=console
|
|
490
731
|
)
|
|
491
732
|
|
|
492
733
|
# Determine if we should prompt for secrets
|
|
@@ -495,8 +736,8 @@ def _handle_mcp_export(
|
|
|
495
736
|
# Warn user if non-interactive mode forces placeholder usage
|
|
496
737
|
if not no_auth_prompt and not sys.stdin.isatty():
|
|
497
738
|
print_markup(
|
|
498
|
-
"[
|
|
499
|
-
"Using placeholder values for secrets.[/
|
|
739
|
+
f"[{WARNING_STYLE}]⚠️ Non-interactive mode detected. "
|
|
740
|
+
"Using placeholder values for secrets.[/]",
|
|
500
741
|
console=console,
|
|
501
742
|
)
|
|
502
743
|
|
|
@@ -531,8 +772,8 @@ def _handle_mcp_export(
|
|
|
531
772
|
write_resource_export(export_path, export_payload, detected_format)
|
|
532
773
|
|
|
533
774
|
print_markup(
|
|
534
|
-
f"[
|
|
535
|
-
f"{export_path} (format: {detected_format})[/
|
|
775
|
+
f"[{SUCCESS_STYLE}]✅ Complete MCP configuration exported to: "
|
|
776
|
+
f"{export_path} (format: {detected_format})[/]",
|
|
536
777
|
console=console,
|
|
537
778
|
)
|
|
538
779
|
|
|
@@ -574,7 +815,7 @@ def _display_mcp_details(ctx: Any, client: Any, mcp: Any) -> None:
|
|
|
574
815
|
)
|
|
575
816
|
else:
|
|
576
817
|
# Fall back to Pydantic model data
|
|
577
|
-
console.print("[
|
|
818
|
+
console.print(f"[{WARNING_STYLE}]Falling back to Pydantic model data[/]")
|
|
578
819
|
result_data = {
|
|
579
820
|
"id": str(getattr(mcp, "id", "N/A")),
|
|
580
821
|
"name": getattr(mcp, "name", "N/A"),
|
|
@@ -680,8 +921,8 @@ def list_tools(ctx: Any, mcp_ref: str) -> None:
|
|
|
680
921
|
|
|
681
922
|
# Define table columns: (data_key, header, style, width)
|
|
682
923
|
columns = [
|
|
683
|
-
("name", "Name",
|
|
684
|
-
("description", "Description",
|
|
924
|
+
("name", "Name", ACCENT_STYLE, None),
|
|
925
|
+
("description", "Description", INFO, 50),
|
|
685
926
|
]
|
|
686
927
|
|
|
687
928
|
# Transform function for safe dictionary access
|
|
@@ -694,7 +935,11 @@ def list_tools(ctx: Any, mcp_ref: str) -> None:
|
|
|
694
935
|
}
|
|
695
936
|
|
|
696
937
|
output_list(
|
|
697
|
-
ctx,
|
|
938
|
+
ctx,
|
|
939
|
+
tools,
|
|
940
|
+
f"{ICON_TOOL} Tools from MCP: {mcp.name}",
|
|
941
|
+
columns,
|
|
942
|
+
transform_tool,
|
|
698
943
|
)
|
|
699
944
|
|
|
700
945
|
except Exception as e:
|
|
@@ -734,7 +979,7 @@ def connect(ctx: Any, config_file: str) -> None:
|
|
|
734
979
|
view = get_ctx_value(ctx, "view", "rich")
|
|
735
980
|
if view != "json":
|
|
736
981
|
print_markup(
|
|
737
|
-
f"[
|
|
982
|
+
f"[{WARNING_STYLE}]Connecting to MCP with config from {config_file}...[/]",
|
|
738
983
|
console=console,
|
|
739
984
|
)
|
|
740
985
|
|
|
@@ -751,10 +996,10 @@ def connect(ctx: Any, config_file: str) -> None:
|
|
|
751
996
|
handle_json_output(ctx, result)
|
|
752
997
|
else:
|
|
753
998
|
success_panel = AIPPanel(
|
|
754
|
-
f"[
|
|
999
|
+
f"[{SUCCESS_STYLE}]✓[/] MCP connection successful!\n\n"
|
|
755
1000
|
f"[bold]Result:[/bold] {result}",
|
|
756
1001
|
title="🔌 Connection",
|
|
757
|
-
border_style=
|
|
1002
|
+
border_style=SUCCESS,
|
|
758
1003
|
)
|
|
759
1004
|
console.print(success_panel)
|
|
760
1005
|
|
|
@@ -762,9 +1007,50 @@ def connect(ctx: Any, config_file: str) -> None:
|
|
|
762
1007
|
raise click.ClickException(str(e))
|
|
763
1008
|
|
|
764
1009
|
|
|
1010
|
+
def _generate_update_preview(
|
|
1011
|
+
mcp: Any, update_data: dict[str, Any], cli_overrides: dict[str, Any]
|
|
1012
|
+
) -> str:
|
|
1013
|
+
"""Generate formatted preview of changes for user confirmation.
|
|
1014
|
+
|
|
1015
|
+
Args:
|
|
1016
|
+
mcp: Current MCP object
|
|
1017
|
+
update_data: Data that will be sent in update request
|
|
1018
|
+
cli_overrides: CLI flags that were explicitly provided
|
|
1019
|
+
|
|
1020
|
+
Returns:
|
|
1021
|
+
Formatted preview string showing old→new values
|
|
1022
|
+
"""
|
|
1023
|
+
lines = [
|
|
1024
|
+
f"\n[bold]The following fields will be updated for MCP '{mcp.name}':[/bold]\n"
|
|
1025
|
+
]
|
|
1026
|
+
|
|
1027
|
+
empty_overrides = []
|
|
1028
|
+
|
|
1029
|
+
# Show each field that will be updated
|
|
1030
|
+
for field, new_value in update_data.items():
|
|
1031
|
+
old_value = getattr(mcp, field, None)
|
|
1032
|
+
|
|
1033
|
+
# Track empty CLI overrides
|
|
1034
|
+
if field in cli_overrides and cli_overrides[field] == "":
|
|
1035
|
+
empty_overrides.append(field)
|
|
1036
|
+
|
|
1037
|
+
old_display = _format_preview_value(old_value)
|
|
1038
|
+
new_display = _format_preview_value(new_value)
|
|
1039
|
+
|
|
1040
|
+
lines.append(f"- [cyan]{field}[/cyan]: {old_display} → {new_display}")
|
|
1041
|
+
|
|
1042
|
+
# Add warnings for empty CLI overrides
|
|
1043
|
+
lines.extend(_build_empty_override_warnings(empty_overrides))
|
|
1044
|
+
|
|
1045
|
+
return "\n".join(lines)
|
|
1046
|
+
|
|
1047
|
+
|
|
765
1048
|
@mcps_group.command()
|
|
766
1049
|
@click.argument("mcp_ref")
|
|
767
1050
|
@click.option("--name", help="New MCP name")
|
|
1051
|
+
@click.option(
|
|
1052
|
+
"--transport", type=click.Choice(["http", "sse"]), help="New transport protocol"
|
|
1053
|
+
)
|
|
768
1054
|
@click.option("--description", help="New description")
|
|
769
1055
|
@click.option(
|
|
770
1056
|
"--config",
|
|
@@ -776,62 +1062,105 @@ def connect(ctx: Any, config_file: str) -> None:
|
|
|
776
1062
|
"auth",
|
|
777
1063
|
help="JSON authentication object or @file reference (e.g., @auth.json)",
|
|
778
1064
|
)
|
|
1065
|
+
@click.option(
|
|
1066
|
+
"--import",
|
|
1067
|
+
"import_file",
|
|
1068
|
+
type=click.Path(exists=True, dir_okay=False, readable=True),
|
|
1069
|
+
help="Import MCP configuration from JSON or YAML export",
|
|
1070
|
+
)
|
|
1071
|
+
@click.option("-y", is_flag=True, help="Skip confirmation prompt when using --import")
|
|
779
1072
|
@output_flags()
|
|
780
1073
|
@click.pass_context
|
|
781
1074
|
def update(
|
|
782
1075
|
ctx: Any,
|
|
783
1076
|
mcp_ref: str,
|
|
784
1077
|
name: str | None,
|
|
1078
|
+
transport: str | None,
|
|
785
1079
|
description: str | None,
|
|
786
1080
|
config: str | None,
|
|
787
1081
|
auth: str | None,
|
|
1082
|
+
import_file: str | None,
|
|
1083
|
+
y: bool,
|
|
788
1084
|
) -> None:
|
|
789
1085
|
"""Update an existing MCP with new configuration values.
|
|
790
1086
|
|
|
1087
|
+
You can update an MCP by providing individual fields via CLI options, or by
|
|
1088
|
+
importing from a file and optionally overriding specific fields.
|
|
1089
|
+
|
|
791
1090
|
Args:
|
|
792
1091
|
ctx: Click context containing output format preferences
|
|
793
1092
|
mcp_ref: MCP reference (ID or name)
|
|
794
1093
|
name: New MCP name (optional)
|
|
1094
|
+
transport: New transport protocol (optional)
|
|
795
1095
|
description: New description (optional)
|
|
796
1096
|
config: New JSON configuration string or @file reference (optional)
|
|
797
1097
|
auth: New JSON authentication object or @file reference (optional)
|
|
1098
|
+
import_file: Optional path to import configuration from export file.
|
|
1099
|
+
CLI options override imported values.
|
|
1100
|
+
y: Skip confirmation prompt when using --import
|
|
798
1101
|
|
|
799
1102
|
Raises:
|
|
800
1103
|
ClickException: If MCP not found, JSON invalid, or no fields specified
|
|
801
1104
|
|
|
802
1105
|
Note:
|
|
803
|
-
|
|
804
|
-
|
|
1106
|
+
Must specify either --import OR at least one CLI field.
|
|
1107
|
+
CLI options override imported values when both are specified.
|
|
1108
|
+
Uses PATCH for import-based updates, PUT/PATCH for CLI-only updates.
|
|
1109
|
+
|
|
1110
|
+
Examples:
|
|
1111
|
+
Update with CLI options:
|
|
1112
|
+
aip mcps update my-mcp --name new-name --transport sse
|
|
1113
|
+
|
|
1114
|
+
Import from file:
|
|
1115
|
+
aip mcps update my-mcp --import mcp-export.json
|
|
1116
|
+
|
|
1117
|
+
Import with overrides:
|
|
1118
|
+
aip mcps update my-mcp --import mcp-export.json --name new-name -y
|
|
805
1119
|
"""
|
|
806
1120
|
try:
|
|
807
1121
|
client = get_client(ctx)
|
|
808
1122
|
|
|
1123
|
+
# 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
|
+
)
|
|
1127
|
+
if not import_file and not cli_flags_provided:
|
|
1128
|
+
raise click.ClickException(
|
|
1129
|
+
"No update fields specified. Use --import file or at least one of: "
|
|
1130
|
+
"--name, --transport, --description, --config, --auth"
|
|
1131
|
+
)
|
|
1132
|
+
|
|
809
1133
|
# Resolve MCP using helper function
|
|
810
1134
|
mcp = _resolve_mcp(ctx, client, mcp_ref)
|
|
811
1135
|
|
|
812
|
-
#
|
|
813
|
-
|
|
814
|
-
if
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
source="--config",
|
|
824
|
-
)
|
|
825
|
-
if auth is not None:
|
|
826
|
-
parsed_auth = parse_json_input(auth)
|
|
827
|
-
update_data["authentication"] = validate_mcp_auth_structure(
|
|
828
|
-
parsed_auth, source="--auth"
|
|
829
|
-
)
|
|
1136
|
+
# Load and validate import data if provided
|
|
1137
|
+
import_payload = None
|
|
1138
|
+
if import_file:
|
|
1139
|
+
import_payload = _load_import_ready_payload(import_file)
|
|
1140
|
+
if not _validate_import_payload_fields(import_payload):
|
|
1141
|
+
return
|
|
1142
|
+
|
|
1143
|
+
# Build update data from import and CLI flags
|
|
1144
|
+
update_data = _build_update_data_from_sources(
|
|
1145
|
+
import_payload, mcp, name, transport, description, config, auth
|
|
1146
|
+
)
|
|
830
1147
|
|
|
831
1148
|
if not update_data:
|
|
832
1149
|
raise click.ClickException("No update fields specified")
|
|
833
1150
|
|
|
834
|
-
#
|
|
1151
|
+
# Show confirmation preview for import-based updates (unless -y flag)
|
|
1152
|
+
if import_payload and not y:
|
|
1153
|
+
cli_overrides = _collect_cli_overrides(
|
|
1154
|
+
name, transport, description, config, auth
|
|
1155
|
+
)
|
|
1156
|
+
preview = _generate_update_preview(mcp, update_data, cli_overrides)
|
|
1157
|
+
print_markup(preview)
|
|
1158
|
+
|
|
1159
|
+
if not click.confirm("\nContinue with update?", default=False):
|
|
1160
|
+
print_markup("[yellow]Update cancelled.[/yellow]")
|
|
1161
|
+
return
|
|
1162
|
+
|
|
1163
|
+
# Update MCP
|
|
835
1164
|
with spinner_context(
|
|
836
1165
|
ctx,
|
|
837
1166
|
"[bold blue]Updating MCP…[/bold blue]",
|
|
@@ -843,10 +1172,7 @@ def update(
|
|
|
843
1172
|
handle_rich_output(ctx, display_update_success("MCP", updated_mcp.name))
|
|
844
1173
|
|
|
845
1174
|
except Exception as e:
|
|
846
|
-
|
|
847
|
-
if get_ctx_value(ctx, "view") != "json":
|
|
848
|
-
display_api_error(e, "MCP update")
|
|
849
|
-
raise click.ClickException(str(e))
|
|
1175
|
+
_handle_cli_error(ctx, e, "MCP update")
|
|
850
1176
|
|
|
851
1177
|
|
|
852
1178
|
@mcps_group.command()
|
|
@@ -896,7 +1222,4 @@ def delete(ctx: Any, mcp_ref: str, yes: bool) -> None:
|
|
|
896
1222
|
handle_rich_output(ctx, display_deletion_success("MCP", mcp.name))
|
|
897
1223
|
|
|
898
1224
|
except Exception as e:
|
|
899
|
-
|
|
900
|
-
if get_ctx_value(ctx, "view") != "json":
|
|
901
|
-
display_api_error(e, "MCP deletion")
|
|
902
|
-
raise click.ClickException(str(e))
|
|
1225
|
+
_handle_cli_error(ctx, e, "MCP deletion")
|
glaip_sdk/cli/commands/models.py
CHANGED
|
@@ -9,6 +9,7 @@ from typing import Any
|
|
|
9
9
|
import click
|
|
10
10
|
from rich.console import Console
|
|
11
11
|
|
|
12
|
+
from glaip_sdk.branding import ACCENT_STYLE, INFO, SUCCESS
|
|
12
13
|
from glaip_sdk.cli.context import output_flags
|
|
13
14
|
from glaip_sdk.cli.utils import (
|
|
14
15
|
get_client,
|
|
@@ -42,9 +43,9 @@ def list_models(ctx: Any) -> None:
|
|
|
42
43
|
# Define table columns: (data_key, header, style, width)
|
|
43
44
|
columns = [
|
|
44
45
|
("id", "ID", "dim", 36),
|
|
45
|
-
("provider", "Provider",
|
|
46
|
-
("name", "Model",
|
|
47
|
-
("base_url", "Base URL",
|
|
46
|
+
("provider", "Provider", ACCENT_STYLE, None),
|
|
47
|
+
("name", "Model", SUCCESS, None),
|
|
48
|
+
("base_url", "Base URL", INFO, None),
|
|
48
49
|
]
|
|
49
50
|
|
|
50
51
|
# Transform function for safe dictionary access
|