glaip-sdk 0.6.5b6__py3-none-any.whl → 0.7.12__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 +42 -5
- glaip_sdk/agents/base.py +217 -42
- glaip_sdk/branding.py +113 -2
- glaip_sdk/cli/account_store.py +15 -0
- glaip_sdk/cli/auth.py +14 -8
- glaip_sdk/cli/commands/accounts.py +1 -1
- glaip_sdk/cli/commands/agents/__init__.py +119 -0
- glaip_sdk/cli/commands/agents/_common.py +561 -0
- glaip_sdk/cli/commands/agents/create.py +151 -0
- glaip_sdk/cli/commands/agents/delete.py +64 -0
- glaip_sdk/cli/commands/agents/get.py +89 -0
- glaip_sdk/cli/commands/agents/list.py +129 -0
- glaip_sdk/cli/commands/agents/run.py +264 -0
- glaip_sdk/cli/commands/agents/sync_langflow.py +72 -0
- glaip_sdk/cli/commands/agents/update.py +112 -0
- glaip_sdk/cli/commands/common_config.py +15 -12
- glaip_sdk/cli/commands/configure.py +2 -3
- glaip_sdk/cli/commands/mcps/__init__.py +94 -0
- glaip_sdk/cli/commands/mcps/_common.py +459 -0
- glaip_sdk/cli/commands/mcps/connect.py +82 -0
- glaip_sdk/cli/commands/mcps/create.py +152 -0
- glaip_sdk/cli/commands/mcps/delete.py +73 -0
- glaip_sdk/cli/commands/mcps/get.py +212 -0
- glaip_sdk/cli/commands/mcps/list.py +69 -0
- glaip_sdk/cli/commands/mcps/tools.py +235 -0
- glaip_sdk/cli/commands/mcps/update.py +190 -0
- glaip_sdk/cli/commands/models.py +2 -4
- glaip_sdk/cli/commands/shared/__init__.py +21 -0
- glaip_sdk/cli/commands/shared/formatters.py +91 -0
- glaip_sdk/cli/commands/tools/__init__.py +69 -0
- glaip_sdk/cli/commands/tools/_common.py +80 -0
- glaip_sdk/cli/commands/tools/create.py +228 -0
- glaip_sdk/cli/commands/tools/delete.py +61 -0
- glaip_sdk/cli/commands/tools/get.py +103 -0
- glaip_sdk/cli/commands/tools/list.py +69 -0
- glaip_sdk/cli/commands/tools/script.py +49 -0
- glaip_sdk/cli/commands/tools/update.py +102 -0
- glaip_sdk/cli/commands/transcripts/__init__.py +90 -0
- glaip_sdk/cli/commands/transcripts/_common.py +9 -0
- glaip_sdk/cli/commands/transcripts/clear.py +5 -0
- glaip_sdk/cli/commands/transcripts/detail.py +5 -0
- glaip_sdk/cli/commands/{transcripts.py → transcripts_original.py} +2 -1
- glaip_sdk/cli/commands/update.py +163 -17
- glaip_sdk/cli/config.py +1 -0
- glaip_sdk/cli/core/output.py +12 -7
- glaip_sdk/cli/entrypoint.py +20 -0
- glaip_sdk/cli/main.py +127 -39
- glaip_sdk/cli/pager.py +3 -3
- glaip_sdk/cli/resolution.py +2 -1
- glaip_sdk/cli/slash/accounts_controller.py +112 -32
- glaip_sdk/cli/slash/agent_session.py +5 -2
- glaip_sdk/cli/slash/prompt.py +11 -0
- glaip_sdk/cli/slash/remote_runs_controller.py +3 -1
- glaip_sdk/cli/slash/session.py +369 -23
- glaip_sdk/cli/slash/tui/__init__.py +26 -1
- glaip_sdk/cli/slash/tui/accounts.tcss +79 -5
- glaip_sdk/cli/slash/tui/accounts_app.py +1027 -88
- glaip_sdk/cli/slash/tui/clipboard.py +195 -0
- glaip_sdk/cli/slash/tui/context.py +87 -0
- glaip_sdk/cli/slash/tui/keybind_registry.py +235 -0
- glaip_sdk/cli/slash/tui/layouts/__init__.py +14 -0
- glaip_sdk/cli/slash/tui/layouts/harlequin.py +160 -0
- glaip_sdk/cli/slash/tui/remote_runs_app.py +119 -12
- glaip_sdk/cli/slash/tui/terminal.py +407 -0
- glaip_sdk/cli/slash/tui/theme/__init__.py +15 -0
- glaip_sdk/cli/slash/tui/theme/catalog.py +79 -0
- glaip_sdk/cli/slash/tui/theme/manager.py +112 -0
- glaip_sdk/cli/slash/tui/theme/tokens.py +55 -0
- glaip_sdk/cli/slash/tui/toast.py +374 -0
- glaip_sdk/cli/transcript/history.py +1 -1
- glaip_sdk/cli/transcript/viewer.py +5 -3
- glaip_sdk/cli/tui_settings.py +125 -0
- glaip_sdk/cli/update_notifier.py +215 -7
- glaip_sdk/cli/validators.py +1 -1
- glaip_sdk/client/__init__.py +2 -1
- glaip_sdk/client/_schedule_payloads.py +89 -0
- glaip_sdk/client/agents.py +50 -8
- glaip_sdk/client/hitl.py +136 -0
- glaip_sdk/client/main.py +7 -1
- glaip_sdk/client/mcps.py +44 -13
- glaip_sdk/client/payloads/agent/__init__.py +23 -0
- glaip_sdk/client/{_agent_payloads.py → payloads/agent/requests.py} +22 -47
- glaip_sdk/client/payloads/agent/responses.py +43 -0
- glaip_sdk/client/run_rendering.py +414 -3
- glaip_sdk/client/schedules.py +439 -0
- glaip_sdk/client/tools.py +57 -26
- glaip_sdk/guardrails/__init__.py +80 -0
- glaip_sdk/guardrails/serializer.py +89 -0
- glaip_sdk/hitl/__init__.py +48 -0
- glaip_sdk/hitl/base.py +64 -0
- glaip_sdk/hitl/callback.py +43 -0
- glaip_sdk/hitl/local.py +121 -0
- glaip_sdk/hitl/remote.py +523 -0
- glaip_sdk/models/__init__.py +17 -0
- glaip_sdk/models/agent_runs.py +2 -1
- glaip_sdk/models/schedule.py +224 -0
- glaip_sdk/payload_schemas/agent.py +1 -0
- glaip_sdk/payload_schemas/guardrails.py +34 -0
- glaip_sdk/registry/tool.py +273 -59
- glaip_sdk/runner/__init__.py +20 -3
- glaip_sdk/runner/deps.py +5 -8
- glaip_sdk/runner/langgraph.py +318 -42
- glaip_sdk/runner/logging_config.py +77 -0
- glaip_sdk/runner/mcp_adapter/langchain_mcp_adapter.py +104 -5
- glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +72 -7
- glaip_sdk/schedules/__init__.py +22 -0
- glaip_sdk/schedules/base.py +291 -0
- glaip_sdk/tools/base.py +67 -14
- glaip_sdk/utils/__init__.py +1 -0
- glaip_sdk/utils/bundler.py +138 -2
- glaip_sdk/utils/import_resolver.py +43 -11
- glaip_sdk/utils/rendering/renderer/base.py +58 -0
- glaip_sdk/utils/runtime_config.py +15 -12
- glaip_sdk/utils/sync.py +31 -11
- glaip_sdk/utils/tool_detection.py +274 -6
- glaip_sdk/utils/tool_storage_provider.py +140 -0
- {glaip_sdk-0.6.5b6.dist-info → glaip_sdk-0.7.12.dist-info}/METADATA +49 -37
- glaip_sdk-0.7.12.dist-info/RECORD +219 -0
- {glaip_sdk-0.6.5b6.dist-info → glaip_sdk-0.7.12.dist-info}/WHEEL +2 -1
- glaip_sdk-0.7.12.dist-info/entry_points.txt +2 -0
- glaip_sdk-0.7.12.dist-info/top_level.txt +1 -0
- glaip_sdk/cli/commands/agents.py +0 -1509
- glaip_sdk/cli/commands/mcps.py +0 -1356
- glaip_sdk/cli/commands/tools.py +0 -576
- glaip_sdk/cli/utils.py +0 -263
- glaip_sdk-0.6.5b6.dist-info/RECORD +0 -159
- glaip_sdk-0.6.5b6.dist-info/entry_points.txt +0 -3
glaip_sdk/cli/commands/mcps.py
DELETED
|
@@ -1,1356 +0,0 @@
|
|
|
1
|
-
"""MCP management commands.
|
|
2
|
-
|
|
3
|
-
Authors:
|
|
4
|
-
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
5
|
-
Putu Ravindra Wiguna (putu.r.wiguna@gdplabs.id)
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
import json
|
|
9
|
-
import sys
|
|
10
|
-
from pathlib import Path
|
|
11
|
-
from typing import Any
|
|
12
|
-
|
|
13
|
-
import click
|
|
14
|
-
from rich.console import Console
|
|
15
|
-
|
|
16
|
-
from glaip_sdk.branding import (
|
|
17
|
-
ACCENT_STYLE,
|
|
18
|
-
INFO,
|
|
19
|
-
SUCCESS,
|
|
20
|
-
SUCCESS_STYLE,
|
|
21
|
-
WARNING_STYLE,
|
|
22
|
-
)
|
|
23
|
-
from glaip_sdk.cli.context import detect_export_format, get_ctx_value, output_flags
|
|
24
|
-
from glaip_sdk.cli.display import (
|
|
25
|
-
display_api_error,
|
|
26
|
-
display_confirmation_prompt,
|
|
27
|
-
display_creation_success,
|
|
28
|
-
display_deletion_success,
|
|
29
|
-
display_update_success,
|
|
30
|
-
handle_json_output,
|
|
31
|
-
handle_rich_output,
|
|
32
|
-
)
|
|
33
|
-
from glaip_sdk.cli.io import (
|
|
34
|
-
fetch_raw_resource_details,
|
|
35
|
-
load_resource_from_file_with_validation,
|
|
36
|
-
)
|
|
37
|
-
from glaip_sdk.cli.mcp_validators import (
|
|
38
|
-
validate_mcp_auth_structure,
|
|
39
|
-
validate_mcp_config_structure,
|
|
40
|
-
)
|
|
41
|
-
from glaip_sdk.cli.parsers.json_input import parse_json_input
|
|
42
|
-
from glaip_sdk.cli.resolution import resolve_resource_reference
|
|
43
|
-
from glaip_sdk.cli.rich_helpers import print_markup
|
|
44
|
-
from glaip_sdk.cli.utils import (
|
|
45
|
-
coerce_to_row,
|
|
46
|
-
fetch_resource_for_export,
|
|
47
|
-
format_datetime_fields,
|
|
48
|
-
get_client,
|
|
49
|
-
output_list,
|
|
50
|
-
output_result,
|
|
51
|
-
spinner_context,
|
|
52
|
-
with_client_and_spinner,
|
|
53
|
-
)
|
|
54
|
-
from glaip_sdk.config.constants import (
|
|
55
|
-
DEFAULT_MCP_TYPE,
|
|
56
|
-
)
|
|
57
|
-
from glaip_sdk.icons import ICON_TOOL
|
|
58
|
-
from glaip_sdk.rich_components import AIPPanel
|
|
59
|
-
from glaip_sdk.utils.import_export import convert_export_to_import_format
|
|
60
|
-
from glaip_sdk.utils.serialization import (
|
|
61
|
-
build_mcp_export_payload,
|
|
62
|
-
write_resource_export,
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
console = Console()
|
|
66
|
-
MAX_DESCRIPTION_LEN = 50
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
def _is_sensitive_data(val: Any) -> bool:
|
|
70
|
-
"""Check if value contains sensitive authentication data.
|
|
71
|
-
|
|
72
|
-
Args:
|
|
73
|
-
val: Value to check for sensitive information
|
|
74
|
-
|
|
75
|
-
Returns:
|
|
76
|
-
True if the value appears to contain sensitive data
|
|
77
|
-
"""
|
|
78
|
-
if not isinstance(val, dict):
|
|
79
|
-
return False
|
|
80
|
-
|
|
81
|
-
sensitive_patterns = {"token", "password", "secret", "key", "credential"}
|
|
82
|
-
return any(pattern in str(k).lower() for k in val.keys() for pattern in sensitive_patterns)
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
def _redact_sensitive_dict(val: dict[str, Any]) -> dict[str, Any]:
|
|
86
|
-
"""Redact sensitive fields from a dictionary.
|
|
87
|
-
|
|
88
|
-
Args:
|
|
89
|
-
val: Dictionary to redact
|
|
90
|
-
|
|
91
|
-
Returns:
|
|
92
|
-
Redacted dictionary
|
|
93
|
-
"""
|
|
94
|
-
redacted = val.copy()
|
|
95
|
-
sensitive_patterns = {"token", "password", "secret", "key", "credential"}
|
|
96
|
-
for k in redacted.keys():
|
|
97
|
-
if any(pattern in k.lower() for pattern in sensitive_patterns):
|
|
98
|
-
redacted[k] = "<REDACTED>"
|
|
99
|
-
return redacted
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
def _format_dict_value(val: dict[str, Any]) -> str:
|
|
103
|
-
"""Format a dictionary value for display.
|
|
104
|
-
|
|
105
|
-
Args:
|
|
106
|
-
val: Dictionary to format
|
|
107
|
-
|
|
108
|
-
Returns:
|
|
109
|
-
Formatted string representation
|
|
110
|
-
"""
|
|
111
|
-
if _is_sensitive_data(val):
|
|
112
|
-
redacted = _redact_sensitive_dict(val)
|
|
113
|
-
return json.dumps(redacted, indent=2)
|
|
114
|
-
return json.dumps(val, indent=2)
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
def _format_preview_value(val: Any) -> str:
|
|
118
|
-
"""Format a value for display in update preview with sensitive data redaction.
|
|
119
|
-
|
|
120
|
-
Args:
|
|
121
|
-
val: Value to format
|
|
122
|
-
|
|
123
|
-
Returns:
|
|
124
|
-
Formatted string representation of the value
|
|
125
|
-
"""
|
|
126
|
-
if val is None:
|
|
127
|
-
return "[dim]None[/dim]"
|
|
128
|
-
if isinstance(val, dict):
|
|
129
|
-
return _format_dict_value(val)
|
|
130
|
-
if isinstance(val, str):
|
|
131
|
-
return f'"{val}"' if val else '""'
|
|
132
|
-
return str(val)
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
def _build_empty_override_warnings(empty_fields: list[str]) -> list[str]:
|
|
136
|
-
"""Build warning lines for empty CLI overrides.
|
|
137
|
-
|
|
138
|
-
Args:
|
|
139
|
-
empty_fields: List of field names with empty string overrides
|
|
140
|
-
|
|
141
|
-
Returns:
|
|
142
|
-
List of formatted warning lines
|
|
143
|
-
"""
|
|
144
|
-
if not empty_fields:
|
|
145
|
-
return []
|
|
146
|
-
|
|
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)
|
|
149
|
-
return warnings
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
def _validate_import_payload_fields(import_payload: dict[str, Any]) -> bool:
|
|
153
|
-
"""Validate that import payload contains updatable fields.
|
|
154
|
-
|
|
155
|
-
Args:
|
|
156
|
-
import_payload: Import payload to validate
|
|
157
|
-
|
|
158
|
-
Returns:
|
|
159
|
-
True if payload has updatable fields, False otherwise
|
|
160
|
-
"""
|
|
161
|
-
updatable_fields = {"name", "transport", "description", "config", "authentication"}
|
|
162
|
-
has_updatable = any(field in import_payload for field in updatable_fields)
|
|
163
|
-
|
|
164
|
-
if not has_updatable:
|
|
165
|
-
available_fields = set(import_payload.keys())
|
|
166
|
-
print_markup(
|
|
167
|
-
"[yellow]⚠️ No updatable fields found in import file.[/yellow]\n"
|
|
168
|
-
f"[dim]Found fields: {', '.join(sorted(available_fields))}[/dim]\n"
|
|
169
|
-
f"[dim]Updatable fields: {', '.join(sorted(updatable_fields))}[/dim]"
|
|
170
|
-
)
|
|
171
|
-
return has_updatable
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
def _get_config_transport(
|
|
175
|
-
transport: str | None,
|
|
176
|
-
import_payload: dict[str, Any] | None,
|
|
177
|
-
mcp: Any,
|
|
178
|
-
) -> str | None:
|
|
179
|
-
"""Get the transport value for config validation.
|
|
180
|
-
|
|
181
|
-
Args:
|
|
182
|
-
transport: CLI transport flag
|
|
183
|
-
import_payload: Optional import payload
|
|
184
|
-
mcp: Current MCP object
|
|
185
|
-
|
|
186
|
-
Returns:
|
|
187
|
-
Transport value or None
|
|
188
|
-
"""
|
|
189
|
-
if import_payload:
|
|
190
|
-
return transport or import_payload.get("transport")
|
|
191
|
-
return transport or getattr(mcp, "transport", None)
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
def _build_update_data_from_sources(
|
|
195
|
-
import_payload: dict[str, Any] | None,
|
|
196
|
-
mcp: Any,
|
|
197
|
-
name: str | None,
|
|
198
|
-
transport: str | None,
|
|
199
|
-
description: str | None,
|
|
200
|
-
config: str | None,
|
|
201
|
-
auth: str | None,
|
|
202
|
-
) -> dict[str, Any]:
|
|
203
|
-
"""Build update data from import payload and CLI flags.
|
|
204
|
-
|
|
205
|
-
Args:
|
|
206
|
-
import_payload: Optional import payload
|
|
207
|
-
mcp: Current MCP object
|
|
208
|
-
name: CLI name flag
|
|
209
|
-
transport: CLI transport flag
|
|
210
|
-
description: CLI description flag
|
|
211
|
-
config: CLI config flag
|
|
212
|
-
auth: CLI auth flag
|
|
213
|
-
|
|
214
|
-
Returns:
|
|
215
|
-
Dictionary with update data
|
|
216
|
-
"""
|
|
217
|
-
update_data = {}
|
|
218
|
-
|
|
219
|
-
# Start with import data if available
|
|
220
|
-
if import_payload:
|
|
221
|
-
updatable_fields = [
|
|
222
|
-
"name",
|
|
223
|
-
"transport",
|
|
224
|
-
"description",
|
|
225
|
-
"config",
|
|
226
|
-
"authentication",
|
|
227
|
-
]
|
|
228
|
-
for field in updatable_fields:
|
|
229
|
-
if field in import_payload:
|
|
230
|
-
update_data[field] = import_payload[field]
|
|
231
|
-
|
|
232
|
-
# CLI flags override import values
|
|
233
|
-
if name is not None:
|
|
234
|
-
update_data["name"] = name
|
|
235
|
-
if transport is not None:
|
|
236
|
-
update_data["transport"] = transport
|
|
237
|
-
if description is not None:
|
|
238
|
-
update_data["description"] = description
|
|
239
|
-
if config is not None:
|
|
240
|
-
parsed_config = parse_json_input(config)
|
|
241
|
-
config_transport = _get_config_transport(transport, import_payload, mcp)
|
|
242
|
-
update_data["config"] = validate_mcp_config_structure(
|
|
243
|
-
parsed_config,
|
|
244
|
-
transport=config_transport,
|
|
245
|
-
source="--config",
|
|
246
|
-
)
|
|
247
|
-
if auth is not None:
|
|
248
|
-
parsed_auth = parse_json_input(auth)
|
|
249
|
-
update_data["authentication"] = validate_mcp_auth_structure(parsed_auth, source="--auth")
|
|
250
|
-
|
|
251
|
-
return update_data
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
def _collect_cli_overrides(
|
|
255
|
-
name: str | None,
|
|
256
|
-
transport: str | None,
|
|
257
|
-
description: str | None,
|
|
258
|
-
config: str | None,
|
|
259
|
-
auth: str | None,
|
|
260
|
-
) -> dict[str, Any]:
|
|
261
|
-
"""Collect CLI flags that were explicitly provided.
|
|
262
|
-
|
|
263
|
-
Args:
|
|
264
|
-
name: CLI name flag
|
|
265
|
-
transport: CLI transport flag
|
|
266
|
-
description: CLI description flag
|
|
267
|
-
config: CLI config flag
|
|
268
|
-
auth: CLI auth flag
|
|
269
|
-
|
|
270
|
-
Returns:
|
|
271
|
-
Dictionary of provided CLI overrides
|
|
272
|
-
"""
|
|
273
|
-
cli_overrides = {}
|
|
274
|
-
if name is not None:
|
|
275
|
-
cli_overrides["name"] = name
|
|
276
|
-
if transport is not None:
|
|
277
|
-
cli_overrides["transport"] = transport
|
|
278
|
-
if description is not None:
|
|
279
|
-
cli_overrides["description"] = description
|
|
280
|
-
if config is not None:
|
|
281
|
-
cli_overrides["config"] = config
|
|
282
|
-
if auth is not None:
|
|
283
|
-
cli_overrides["auth"] = auth
|
|
284
|
-
return cli_overrides
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
def _handle_cli_error(ctx: Any, error: Exception, operation: str) -> None:
|
|
288
|
-
"""Render CLI error once and exit with non-zero status."""
|
|
289
|
-
handle_json_output(ctx, error=error)
|
|
290
|
-
if get_ctx_value(ctx, "view") != "json":
|
|
291
|
-
display_api_error(error, operation)
|
|
292
|
-
ctx.exit(1)
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
@click.group(name="mcps", no_args_is_help=True)
|
|
296
|
-
def mcps_group() -> None:
|
|
297
|
-
"""MCP management operations.
|
|
298
|
-
|
|
299
|
-
Provides commands for creating, listing, updating, deleting, and managing
|
|
300
|
-
Model Context Protocol (MCP) configurations.
|
|
301
|
-
"""
|
|
302
|
-
pass
|
|
303
|
-
|
|
304
|
-
|
|
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.
|
|
311
|
-
|
|
312
|
-
Args:
|
|
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).
|
|
317
|
-
|
|
318
|
-
Returns:
|
|
319
|
-
MCP instance if resolution succeeds, None if not found.
|
|
320
|
-
|
|
321
|
-
Raises:
|
|
322
|
-
click.ClickException: When resolution fails or selection is invalid.
|
|
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
|
|
329
|
-
return resolve_resource_reference(
|
|
330
|
-
ctx,
|
|
331
|
-
client,
|
|
332
|
-
ref,
|
|
333
|
-
"mcp",
|
|
334
|
-
get_by_id_func,
|
|
335
|
-
find_by_name_func,
|
|
336
|
-
"MCP",
|
|
337
|
-
select=select,
|
|
338
|
-
)
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
def _strip_server_only_fields(import_data: dict[str, Any]) -> dict[str, Any]:
|
|
342
|
-
"""Remove fields that should not be forwarded during import-driven creation.
|
|
343
|
-
|
|
344
|
-
Args:
|
|
345
|
-
import_data: Raw import payload loaded from disk.
|
|
346
|
-
|
|
347
|
-
Returns:
|
|
348
|
-
A shallow copy of the data with server-managed fields removed.
|
|
349
|
-
"""
|
|
350
|
-
cleaned = dict(import_data)
|
|
351
|
-
for key in (
|
|
352
|
-
"id",
|
|
353
|
-
"type",
|
|
354
|
-
"status",
|
|
355
|
-
"connection_status",
|
|
356
|
-
"created_at",
|
|
357
|
-
"updated_at",
|
|
358
|
-
):
|
|
359
|
-
cleaned.pop(key, None)
|
|
360
|
-
return cleaned
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
def _load_import_ready_payload(import_file: str) -> dict[str, Any]:
|
|
364
|
-
"""Load and normalise an imported MCP definition for create operations.
|
|
365
|
-
|
|
366
|
-
Args:
|
|
367
|
-
import_file: Path to an MCP export file (JSON or YAML).
|
|
368
|
-
|
|
369
|
-
Returns:
|
|
370
|
-
Normalised import payload ready for CLI/REST usage.
|
|
371
|
-
|
|
372
|
-
Raises:
|
|
373
|
-
click.ClickException: If the file cannot be parsed or validated.
|
|
374
|
-
"""
|
|
375
|
-
raw_data = load_resource_from_file_with_validation(Path(import_file), "MCP")
|
|
376
|
-
import_data = convert_export_to_import_format(raw_data)
|
|
377
|
-
import_data = _strip_server_only_fields(import_data)
|
|
378
|
-
|
|
379
|
-
transport = import_data.get("transport")
|
|
380
|
-
|
|
381
|
-
if "config" in import_data:
|
|
382
|
-
import_data["config"] = validate_mcp_config_structure(
|
|
383
|
-
import_data["config"],
|
|
384
|
-
transport=transport,
|
|
385
|
-
source="import file",
|
|
386
|
-
)
|
|
387
|
-
|
|
388
|
-
if "authentication" in import_data:
|
|
389
|
-
import_data["authentication"] = validate_mcp_auth_structure(
|
|
390
|
-
import_data["authentication"],
|
|
391
|
-
source="import file",
|
|
392
|
-
)
|
|
393
|
-
|
|
394
|
-
return import_data
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
def _coerce_cli_string(value: str | None) -> str | None:
|
|
398
|
-
"""Normalise CLI string values so blanks are treated as missing.
|
|
399
|
-
|
|
400
|
-
Args:
|
|
401
|
-
value: User-provided string option.
|
|
402
|
-
|
|
403
|
-
Returns:
|
|
404
|
-
The stripped string, or ``None`` when the value is blank/whitespace-only.
|
|
405
|
-
"""
|
|
406
|
-
if value is None:
|
|
407
|
-
return None
|
|
408
|
-
trimmed = value.strip()
|
|
409
|
-
# Treat whitespace-only strings as None
|
|
410
|
-
return trimmed if trimmed else None
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
def _merge_config_field(
|
|
414
|
-
merged_base: dict[str, Any],
|
|
415
|
-
cli_config: str | None,
|
|
416
|
-
final_transport: str | None,
|
|
417
|
-
) -> None:
|
|
418
|
-
"""Merge config field with validation.
|
|
419
|
-
|
|
420
|
-
Args:
|
|
421
|
-
merged_base: Base payload to update in-place.
|
|
422
|
-
cli_config: Raw CLI JSON string for config.
|
|
423
|
-
final_transport: Transport type for validation.
|
|
424
|
-
|
|
425
|
-
Raises:
|
|
426
|
-
click.ClickException: If config JSON parsing or validation fails.
|
|
427
|
-
"""
|
|
428
|
-
if cli_config is not None:
|
|
429
|
-
parsed_config = parse_json_input(cli_config)
|
|
430
|
-
merged_base["config"] = validate_mcp_config_structure(
|
|
431
|
-
parsed_config,
|
|
432
|
-
transport=final_transport,
|
|
433
|
-
source="--config",
|
|
434
|
-
)
|
|
435
|
-
elif "config" not in merged_base or merged_base["config"] is None:
|
|
436
|
-
merged_base["config"] = {}
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
def _merge_auth_field(
|
|
440
|
-
merged_base: dict[str, Any],
|
|
441
|
-
cli_auth: str | None,
|
|
442
|
-
) -> None:
|
|
443
|
-
"""Merge authentication field with validation.
|
|
444
|
-
|
|
445
|
-
Args:
|
|
446
|
-
merged_base: Base payload to update in-place.
|
|
447
|
-
cli_auth: Raw CLI JSON string for authentication.
|
|
448
|
-
|
|
449
|
-
Raises:
|
|
450
|
-
click.ClickException: If auth JSON parsing or validation fails.
|
|
451
|
-
"""
|
|
452
|
-
if cli_auth is not None:
|
|
453
|
-
parsed_auth = parse_json_input(cli_auth)
|
|
454
|
-
merged_base["authentication"] = validate_mcp_auth_structure(
|
|
455
|
-
parsed_auth,
|
|
456
|
-
source="--auth",
|
|
457
|
-
)
|
|
458
|
-
elif "authentication" not in merged_base:
|
|
459
|
-
merged_base["authentication"] = None
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
def _merge_import_payload(
|
|
463
|
-
import_data: dict[str, Any] | None,
|
|
464
|
-
*,
|
|
465
|
-
cli_name: str | None,
|
|
466
|
-
cli_transport: str | None,
|
|
467
|
-
cli_description: str | None,
|
|
468
|
-
cli_config: str | None,
|
|
469
|
-
cli_auth: str | None,
|
|
470
|
-
) -> tuple[dict[str, Any], list[str]]:
|
|
471
|
-
"""Merge import data with CLI overrides while tracking missing fields.
|
|
472
|
-
|
|
473
|
-
Args:
|
|
474
|
-
import_data: Normalised payload loaded from file (if provided).
|
|
475
|
-
cli_name: Name supplied via CLI option.
|
|
476
|
-
cli_transport: Transport supplied via CLI option.
|
|
477
|
-
cli_description: Description supplied via CLI option.
|
|
478
|
-
cli_config: Raw CLI JSON string for config.
|
|
479
|
-
cli_auth: Raw CLI JSON string for authentication.
|
|
480
|
-
|
|
481
|
-
Returns:
|
|
482
|
-
A tuple of (merged_payload, missing_required_fields).
|
|
483
|
-
|
|
484
|
-
Raises:
|
|
485
|
-
click.ClickException: If config/auth JSON parsing or validation fails.
|
|
486
|
-
"""
|
|
487
|
-
merged_base = import_data.copy() if import_data else {}
|
|
488
|
-
|
|
489
|
-
# Merge simple string fields using truthy CLI overrides
|
|
490
|
-
for field, cli_value in (
|
|
491
|
-
("name", _coerce_cli_string(cli_name)),
|
|
492
|
-
("transport", _coerce_cli_string(cli_transport)),
|
|
493
|
-
("description", _coerce_cli_string(cli_description)),
|
|
494
|
-
):
|
|
495
|
-
if cli_value is not None:
|
|
496
|
-
merged_base[field] = cli_value
|
|
497
|
-
|
|
498
|
-
# Determine final transport before validating config
|
|
499
|
-
final_transport = merged_base.get("transport")
|
|
500
|
-
|
|
501
|
-
# Merge config and authentication with validation
|
|
502
|
-
_merge_config_field(merged_base, cli_config, final_transport)
|
|
503
|
-
_merge_auth_field(merged_base, cli_auth)
|
|
504
|
-
|
|
505
|
-
# Validate required fields
|
|
506
|
-
missing_fields = []
|
|
507
|
-
for required in ("name", "transport"):
|
|
508
|
-
value = merged_base.get(required)
|
|
509
|
-
if not isinstance(value, str) or not value.strip():
|
|
510
|
-
missing_fields.append(required)
|
|
511
|
-
|
|
512
|
-
return merged_base, missing_fields
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
@mcps_group.command(name="list")
|
|
516
|
-
@output_flags()
|
|
517
|
-
@click.pass_context
|
|
518
|
-
def list_mcps(ctx: Any) -> None:
|
|
519
|
-
"""List all MCPs in a formatted table.
|
|
520
|
-
|
|
521
|
-
Args:
|
|
522
|
-
ctx: Click context containing output format preferences
|
|
523
|
-
|
|
524
|
-
Raises:
|
|
525
|
-
ClickException: If API request fails
|
|
526
|
-
"""
|
|
527
|
-
try:
|
|
528
|
-
with with_client_and_spinner(
|
|
529
|
-
ctx,
|
|
530
|
-
"[bold blue]Fetching MCPs…[/bold blue]",
|
|
531
|
-
console_override=console,
|
|
532
|
-
) as client:
|
|
533
|
-
mcps = client.mcps.list_mcps()
|
|
534
|
-
|
|
535
|
-
# Define table columns: (data_key, header, style, width)
|
|
536
|
-
columns = [
|
|
537
|
-
("id", "ID", "dim", 36),
|
|
538
|
-
("name", "Name", ACCENT_STYLE, None),
|
|
539
|
-
("config", "Config", INFO, None),
|
|
540
|
-
]
|
|
541
|
-
|
|
542
|
-
# Transform function for safe dictionary access
|
|
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
|
-
"""
|
|
552
|
-
row = coerce_to_row(mcp, ["id", "name", "config"])
|
|
553
|
-
# Ensure id is always a string
|
|
554
|
-
row["id"] = str(row["id"])
|
|
555
|
-
# Truncate config field for display
|
|
556
|
-
if row["config"] != "N/A":
|
|
557
|
-
row["config"] = str(row["config"])[:50] + "..." if len(str(row["config"])) > 50 else str(row["config"])
|
|
558
|
-
return row
|
|
559
|
-
|
|
560
|
-
output_list(ctx, mcps, "🔌 Available MCPs", columns, transform_mcp)
|
|
561
|
-
|
|
562
|
-
except Exception as e:
|
|
563
|
-
raise click.ClickException(str(e)) from e
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
@mcps_group.command()
|
|
567
|
-
@click.option("--name", help="MCP name")
|
|
568
|
-
@click.option("--transport", help="MCP transport protocol")
|
|
569
|
-
@click.option("--description", help="MCP description")
|
|
570
|
-
@click.option(
|
|
571
|
-
"--config",
|
|
572
|
-
help="JSON configuration string or @file reference (e.g., @config.json)",
|
|
573
|
-
)
|
|
574
|
-
@click.option(
|
|
575
|
-
"--auth",
|
|
576
|
-
"--authentication",
|
|
577
|
-
"auth",
|
|
578
|
-
help="JSON authentication object or @file reference (e.g., @auth.json)",
|
|
579
|
-
)
|
|
580
|
-
@click.option(
|
|
581
|
-
"--import",
|
|
582
|
-
"import_file",
|
|
583
|
-
type=click.Path(exists=True, dir_okay=False),
|
|
584
|
-
help="Import MCP configuration from JSON or YAML export",
|
|
585
|
-
)
|
|
586
|
-
@output_flags()
|
|
587
|
-
@click.pass_context
|
|
588
|
-
def create(
|
|
589
|
-
ctx: Any,
|
|
590
|
-
name: str | None,
|
|
591
|
-
transport: str | None,
|
|
592
|
-
description: str | None,
|
|
593
|
-
config: str | None,
|
|
594
|
-
auth: str | None,
|
|
595
|
-
import_file: str | None,
|
|
596
|
-
) -> None:
|
|
597
|
-
r"""Create a new MCP with specified configuration.
|
|
598
|
-
|
|
599
|
-
You can create an MCP by providing all parameters via CLI options, or by
|
|
600
|
-
importing from a file and optionally overriding specific fields.
|
|
601
|
-
|
|
602
|
-
Args:
|
|
603
|
-
ctx: Click context containing output format preferences
|
|
604
|
-
name: MCP name (required unless provided via --import)
|
|
605
|
-
transport: MCP transport protocol (required unless provided via --import)
|
|
606
|
-
description: Optional MCP description
|
|
607
|
-
config: JSON configuration string or @file reference
|
|
608
|
-
auth: JSON authentication object or @file reference
|
|
609
|
-
import_file: Optional path to import configuration from export file.
|
|
610
|
-
CLI options override imported values.
|
|
611
|
-
|
|
612
|
-
Raises:
|
|
613
|
-
ClickException: If JSON parsing fails or API request fails
|
|
614
|
-
|
|
615
|
-
\b
|
|
616
|
-
Examples:
|
|
617
|
-
Create from CLI options:
|
|
618
|
-
aip mcps create --name my-mcp --transport http --config '{"url": "https://api.example.com"}'
|
|
619
|
-
|
|
620
|
-
Import from file:
|
|
621
|
-
aip mcps create --import mcp-export.json
|
|
622
|
-
|
|
623
|
-
Import with overrides:
|
|
624
|
-
aip mcps create --import mcp-export.json --name new-name --transport sse
|
|
625
|
-
"""
|
|
626
|
-
try:
|
|
627
|
-
# Get API client instance for MCP operations
|
|
628
|
-
api_client = get_client(ctx)
|
|
629
|
-
|
|
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
|
|
632
|
-
|
|
633
|
-
merged_payload, missing_fields = _merge_import_payload(
|
|
634
|
-
import_payload,
|
|
635
|
-
cli_name=name,
|
|
636
|
-
cli_transport=transport,
|
|
637
|
-
cli_description=description,
|
|
638
|
-
cli_config=config,
|
|
639
|
-
cli_auth=auth,
|
|
640
|
-
)
|
|
641
|
-
|
|
642
|
-
if missing_fields:
|
|
643
|
-
raise click.ClickException(
|
|
644
|
-
"Missing required fields after combining import and CLI values: " + ", ".join(missing_fields)
|
|
645
|
-
)
|
|
646
|
-
|
|
647
|
-
effective_name = merged_payload["name"]
|
|
648
|
-
effective_transport = merged_payload["transport"]
|
|
649
|
-
effective_description = merged_payload.get("description")
|
|
650
|
-
effective_config = merged_payload.get("config") or {}
|
|
651
|
-
effective_auth = merged_payload.get("authentication")
|
|
652
|
-
|
|
653
|
-
with spinner_context(
|
|
654
|
-
ctx,
|
|
655
|
-
"[bold blue]Creating MCP…[/bold blue]",
|
|
656
|
-
console_override=console,
|
|
657
|
-
):
|
|
658
|
-
create_kwargs: dict[str, Any] = {
|
|
659
|
-
"name": effective_name,
|
|
660
|
-
"config": effective_config,
|
|
661
|
-
"transport": effective_transport,
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
if effective_description is not None:
|
|
665
|
-
create_kwargs["description"] = effective_description
|
|
666
|
-
|
|
667
|
-
if effective_auth:
|
|
668
|
-
create_kwargs["authentication"] = effective_auth
|
|
669
|
-
|
|
670
|
-
mcp_metadata = merged_payload.get("mcp_metadata")
|
|
671
|
-
if mcp_metadata is not None:
|
|
672
|
-
create_kwargs["mcp_metadata"] = mcp_metadata
|
|
673
|
-
|
|
674
|
-
mcp = api_client.mcps.create_mcp(**create_kwargs)
|
|
675
|
-
|
|
676
|
-
# Handle JSON output
|
|
677
|
-
handle_json_output(ctx, mcp.model_dump())
|
|
678
|
-
|
|
679
|
-
# Handle Rich output
|
|
680
|
-
rich_panel = display_creation_success(
|
|
681
|
-
"MCP",
|
|
682
|
-
mcp.name,
|
|
683
|
-
mcp.id,
|
|
684
|
-
Type=getattr(mcp, "type", DEFAULT_MCP_TYPE),
|
|
685
|
-
Transport=getattr(mcp, "transport", effective_transport),
|
|
686
|
-
Description=effective_description or "No description",
|
|
687
|
-
)
|
|
688
|
-
handle_rich_output(ctx, rich_panel)
|
|
689
|
-
|
|
690
|
-
except Exception as e:
|
|
691
|
-
_handle_cli_error(ctx, e, "MCP creation")
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
def _handle_mcp_export(
|
|
695
|
-
ctx: Any,
|
|
696
|
-
client: Any,
|
|
697
|
-
mcp: Any,
|
|
698
|
-
export_path: Path,
|
|
699
|
-
no_auth_prompt: bool,
|
|
700
|
-
auth_placeholder: str,
|
|
701
|
-
) -> None:
|
|
702
|
-
"""Handle MCP export to file with format detection and auth handling.
|
|
703
|
-
|
|
704
|
-
Args:
|
|
705
|
-
ctx: Click context for spinner management
|
|
706
|
-
client: API client for fetching MCP details
|
|
707
|
-
mcp: MCP object to export
|
|
708
|
-
export_path: Target file path (format detected from extension)
|
|
709
|
-
no_auth_prompt: Skip interactive secret prompts if True
|
|
710
|
-
auth_placeholder: Placeholder text for missing secrets
|
|
711
|
-
|
|
712
|
-
Note:
|
|
713
|
-
Supports JSON (.json) and YAML (.yaml/.yml) export formats.
|
|
714
|
-
In interactive mode, prompts for secret values.
|
|
715
|
-
In non-interactive mode, uses placeholder values.
|
|
716
|
-
"""
|
|
717
|
-
# Auto-detect format from file extension
|
|
718
|
-
detected_format = detect_export_format(export_path)
|
|
719
|
-
|
|
720
|
-
# Always export comprehensive data - re-fetch with full details
|
|
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
|
-
)
|
|
729
|
-
|
|
730
|
-
# Determine if we should prompt for secrets
|
|
731
|
-
prompt_for_secrets = not no_auth_prompt and sys.stdin.isatty()
|
|
732
|
-
|
|
733
|
-
# Warn user if non-interactive mode forces placeholder usage
|
|
734
|
-
if not no_auth_prompt and not sys.stdin.isatty():
|
|
735
|
-
print_markup(
|
|
736
|
-
f"[{WARNING_STYLE}]⚠️ Non-interactive mode detected. Using placeholder values for secrets.[/]",
|
|
737
|
-
console=console,
|
|
738
|
-
)
|
|
739
|
-
|
|
740
|
-
# Build and write export payload
|
|
741
|
-
if prompt_for_secrets:
|
|
742
|
-
# Interactive mode: no spinner during prompts
|
|
743
|
-
export_payload = build_mcp_export_payload(
|
|
744
|
-
mcp,
|
|
745
|
-
prompt_for_secrets=prompt_for_secrets,
|
|
746
|
-
placeholder=auth_placeholder,
|
|
747
|
-
console=console,
|
|
748
|
-
)
|
|
749
|
-
with spinner_context(
|
|
750
|
-
ctx,
|
|
751
|
-
"[bold blue]Writing export file…[/bold blue]",
|
|
752
|
-
console_override=console,
|
|
753
|
-
):
|
|
754
|
-
write_resource_export(export_path, export_payload, detected_format)
|
|
755
|
-
else:
|
|
756
|
-
# Non-interactive mode: spinner for entire export process
|
|
757
|
-
with spinner_context(
|
|
758
|
-
ctx,
|
|
759
|
-
"[bold blue]Exporting MCP configuration…[/bold blue]",
|
|
760
|
-
console_override=console,
|
|
761
|
-
):
|
|
762
|
-
export_payload = build_mcp_export_payload(
|
|
763
|
-
mcp,
|
|
764
|
-
prompt_for_secrets=prompt_for_secrets,
|
|
765
|
-
placeholder=auth_placeholder,
|
|
766
|
-
console=console,
|
|
767
|
-
)
|
|
768
|
-
write_resource_export(export_path, export_payload, detected_format)
|
|
769
|
-
|
|
770
|
-
print_markup(
|
|
771
|
-
f"[{SUCCESS_STYLE}]✅ Complete MCP configuration exported to: {export_path} (format: {detected_format})[/]",
|
|
772
|
-
console=console,
|
|
773
|
-
)
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
def _display_mcp_details(ctx: Any, client: Any, mcp: Any) -> None:
|
|
777
|
-
"""Display MCP details using raw API data or fallback to Pydantic model.
|
|
778
|
-
|
|
779
|
-
Args:
|
|
780
|
-
ctx: Click context containing output format preferences
|
|
781
|
-
client: API client for fetching raw MCP data
|
|
782
|
-
mcp: MCP object to display details for
|
|
783
|
-
|
|
784
|
-
Note:
|
|
785
|
-
Attempts to fetch raw API data first to preserve all fields.
|
|
786
|
-
Falls back to Pydantic model data if raw data unavailable.
|
|
787
|
-
Formats datetime fields for better readability.
|
|
788
|
-
"""
|
|
789
|
-
# Try to fetch raw API data first to preserve ALL fields
|
|
790
|
-
with spinner_context(
|
|
791
|
-
ctx,
|
|
792
|
-
"[bold blue]Fetching detailed MCP data…[/bold blue]",
|
|
793
|
-
console_override=console,
|
|
794
|
-
):
|
|
795
|
-
raw_mcp_data = fetch_raw_resource_details(client, mcp, "mcps")
|
|
796
|
-
|
|
797
|
-
if raw_mcp_data:
|
|
798
|
-
# Use raw API data - this preserves ALL fields
|
|
799
|
-
formatted_data = format_datetime_fields(raw_mcp_data)
|
|
800
|
-
|
|
801
|
-
output_result(
|
|
802
|
-
ctx,
|
|
803
|
-
formatted_data,
|
|
804
|
-
title="MCP Details",
|
|
805
|
-
panel_title=f"🔌 {raw_mcp_data.get('name', 'Unknown')}",
|
|
806
|
-
)
|
|
807
|
-
else:
|
|
808
|
-
# Fall back to Pydantic model data
|
|
809
|
-
console.print(f"[{WARNING_STYLE}]Falling back to Pydantic model data[/]")
|
|
810
|
-
result_data = {
|
|
811
|
-
"id": str(getattr(mcp, "id", "N/A")),
|
|
812
|
-
"name": getattr(mcp, "name", "N/A"),
|
|
813
|
-
"type": getattr(mcp, "type", "N/A"),
|
|
814
|
-
"config": getattr(mcp, "config", "N/A"),
|
|
815
|
-
"status": getattr(mcp, "status", "N/A"),
|
|
816
|
-
"connection_status": getattr(mcp, "connection_status", "N/A"),
|
|
817
|
-
}
|
|
818
|
-
output_result(ctx, result_data, title=f"🔌 {mcp.name}")
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
@mcps_group.command()
|
|
822
|
-
@click.argument("mcp_ref")
|
|
823
|
-
@click.option(
|
|
824
|
-
"--export",
|
|
825
|
-
type=click.Path(dir_okay=False, writable=True),
|
|
826
|
-
help="Export complete MCP configuration to file (format auto-detected from .json/.yaml extension)",
|
|
827
|
-
)
|
|
828
|
-
@click.option(
|
|
829
|
-
"--no-auth-prompt",
|
|
830
|
-
is_flag=True,
|
|
831
|
-
help="Skip interactive secret prompts and use placeholder values.",
|
|
832
|
-
)
|
|
833
|
-
@click.option(
|
|
834
|
-
"--auth-placeholder",
|
|
835
|
-
default="<INSERT VALUE>",
|
|
836
|
-
show_default=True,
|
|
837
|
-
help="Placeholder text used when secrets are unavailable.",
|
|
838
|
-
)
|
|
839
|
-
@output_flags()
|
|
840
|
-
@click.pass_context
|
|
841
|
-
def get(
|
|
842
|
-
ctx: Any,
|
|
843
|
-
mcp_ref: str,
|
|
844
|
-
export: str | None,
|
|
845
|
-
no_auth_prompt: bool,
|
|
846
|
-
auth_placeholder: str,
|
|
847
|
-
) -> None:
|
|
848
|
-
r"""Get MCP details and optionally export configuration to file.
|
|
849
|
-
|
|
850
|
-
Args:
|
|
851
|
-
ctx: Click context containing output format preferences
|
|
852
|
-
mcp_ref: MCP reference (ID or name)
|
|
853
|
-
export: Optional file path to export MCP configuration
|
|
854
|
-
no_auth_prompt: Skip interactive secret prompts if True
|
|
855
|
-
auth_placeholder: Placeholder text for missing secrets
|
|
856
|
-
|
|
857
|
-
Raises:
|
|
858
|
-
ClickException: If MCP not found or export fails
|
|
859
|
-
|
|
860
|
-
\b
|
|
861
|
-
Examples:
|
|
862
|
-
aip mcps get my-mcp
|
|
863
|
-
aip mcps get my-mcp --export mcp.json # Export as JSON
|
|
864
|
-
aip mcps get my-mcp --export mcp.yaml # Export as YAML
|
|
865
|
-
"""
|
|
866
|
-
try:
|
|
867
|
-
client = get_client(ctx)
|
|
868
|
-
|
|
869
|
-
# Resolve MCP using helper function
|
|
870
|
-
mcp = _resolve_mcp(ctx, client, mcp_ref)
|
|
871
|
-
|
|
872
|
-
# Handle export option
|
|
873
|
-
if export:
|
|
874
|
-
_handle_mcp_export(ctx, client, mcp, Path(export), no_auth_prompt, auth_placeholder)
|
|
875
|
-
|
|
876
|
-
# Display MCP details
|
|
877
|
-
_display_mcp_details(ctx, client, mcp)
|
|
878
|
-
|
|
879
|
-
except Exception as 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
|
-
)
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
@mcps_group.command("tools")
|
|
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
|
-
)
|
|
1050
|
-
@output_flags()
|
|
1051
|
-
@click.pass_context
|
|
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.
|
|
1054
|
-
|
|
1055
|
-
Args:
|
|
1056
|
-
ctx: Click context containing output format preferences
|
|
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
|
|
1060
|
-
|
|
1061
|
-
Raises:
|
|
1062
|
-
ClickException: If MCP not found or tools fetch fails
|
|
1063
|
-
|
|
1064
|
-
Examples:
|
|
1065
|
-
Get tools from saved MCP:
|
|
1066
|
-
aip mcps tools <MCP_ID>
|
|
1067
|
-
|
|
1068
|
-
Get tools from config file (without saving to DB):
|
|
1069
|
-
aip mcps tools --from-config mcp-config.json
|
|
1070
|
-
|
|
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)
|
|
1077
|
-
|
|
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)
|
|
1082
|
-
|
|
1083
|
-
if names_only:
|
|
1084
|
-
_output_tool_names(ctx, tools)
|
|
1085
|
-
else:
|
|
1086
|
-
_output_tools_table(ctx, tools, title)
|
|
1087
|
-
|
|
1088
|
-
except Exception as e:
|
|
1089
|
-
raise click.ClickException(str(e)) from e
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
@mcps_group.command("connect")
|
|
1093
|
-
@click.option(
|
|
1094
|
-
"--from-file",
|
|
1095
|
-
"config_file",
|
|
1096
|
-
required=True,
|
|
1097
|
-
help="MCP config JSON file",
|
|
1098
|
-
)
|
|
1099
|
-
@output_flags()
|
|
1100
|
-
@click.pass_context
|
|
1101
|
-
def connect(ctx: Any, config_file: str) -> None:
|
|
1102
|
-
"""Test MCP connection using a configuration file.
|
|
1103
|
-
|
|
1104
|
-
Args:
|
|
1105
|
-
ctx: Click context containing output format preferences
|
|
1106
|
-
config_file: Path to MCP configuration JSON file
|
|
1107
|
-
|
|
1108
|
-
Raises:
|
|
1109
|
-
ClickException: If config file invalid or connection test fails
|
|
1110
|
-
|
|
1111
|
-
Note:
|
|
1112
|
-
Loads MCP configuration from JSON file and tests connectivity.
|
|
1113
|
-
Displays success or failure with connection details.
|
|
1114
|
-
"""
|
|
1115
|
-
try:
|
|
1116
|
-
client = get_client(ctx)
|
|
1117
|
-
|
|
1118
|
-
# Load MCP config from file
|
|
1119
|
-
with open(config_file) as f:
|
|
1120
|
-
config = json.load(f)
|
|
1121
|
-
|
|
1122
|
-
view = get_ctx_value(ctx, "view", "rich")
|
|
1123
|
-
if view != "json":
|
|
1124
|
-
print_markup(
|
|
1125
|
-
f"[{WARNING_STYLE}]Connecting to MCP with config from {config_file}...[/]",
|
|
1126
|
-
console=console,
|
|
1127
|
-
)
|
|
1128
|
-
|
|
1129
|
-
# Test connection using config
|
|
1130
|
-
with spinner_context(
|
|
1131
|
-
ctx,
|
|
1132
|
-
"[bold blue]Connecting to MCP…[/bold blue]",
|
|
1133
|
-
console_override=console,
|
|
1134
|
-
):
|
|
1135
|
-
result = client.mcps.test_mcp_connection_from_config(config)
|
|
1136
|
-
|
|
1137
|
-
view = get_ctx_value(ctx, "view", "rich")
|
|
1138
|
-
if view == "json":
|
|
1139
|
-
handle_json_output(ctx, result)
|
|
1140
|
-
else:
|
|
1141
|
-
success_panel = AIPPanel(
|
|
1142
|
-
f"[{SUCCESS_STYLE}]✓[/] MCP connection successful!\n\n[bold]Result:[/bold] {result}",
|
|
1143
|
-
title="🔌 Connection",
|
|
1144
|
-
border_style=SUCCESS,
|
|
1145
|
-
)
|
|
1146
|
-
console.print(success_panel)
|
|
1147
|
-
|
|
1148
|
-
except Exception as e:
|
|
1149
|
-
raise click.ClickException(str(e)) from e
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
def _generate_update_preview(mcp: Any, update_data: dict[str, Any], cli_overrides: dict[str, Any]) -> str:
|
|
1153
|
-
"""Generate formatted preview of changes for user confirmation.
|
|
1154
|
-
|
|
1155
|
-
Args:
|
|
1156
|
-
mcp: Current MCP object
|
|
1157
|
-
update_data: Data that will be sent in update request
|
|
1158
|
-
cli_overrides: CLI flags that were explicitly provided
|
|
1159
|
-
|
|
1160
|
-
Returns:
|
|
1161
|
-
Formatted preview string showing old→new values
|
|
1162
|
-
"""
|
|
1163
|
-
lines = [f"\n[bold]The following fields will be updated for MCP '{mcp.name}':[/bold]\n"]
|
|
1164
|
-
|
|
1165
|
-
empty_overrides = []
|
|
1166
|
-
|
|
1167
|
-
# Show each field that will be updated
|
|
1168
|
-
for field, new_value in update_data.items():
|
|
1169
|
-
old_value = getattr(mcp, field, None)
|
|
1170
|
-
|
|
1171
|
-
# Track empty CLI overrides
|
|
1172
|
-
if field in cli_overrides and cli_overrides[field] == "":
|
|
1173
|
-
empty_overrides.append(field)
|
|
1174
|
-
|
|
1175
|
-
old_display = _format_preview_value(old_value)
|
|
1176
|
-
new_display = _format_preview_value(new_value)
|
|
1177
|
-
|
|
1178
|
-
lines.append(f"- [cyan]{field}[/cyan]: {old_display} → {new_display}")
|
|
1179
|
-
|
|
1180
|
-
# Add warnings for empty CLI overrides
|
|
1181
|
-
lines.extend(_build_empty_override_warnings(empty_overrides))
|
|
1182
|
-
|
|
1183
|
-
return "\n".join(lines)
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
@mcps_group.command()
|
|
1187
|
-
@click.argument("mcp_ref")
|
|
1188
|
-
@click.option("--name", help="New MCP name")
|
|
1189
|
-
@click.option("--transport", type=click.Choice(["http", "sse"]), help="New transport protocol")
|
|
1190
|
-
@click.option("--description", help="New description")
|
|
1191
|
-
@click.option(
|
|
1192
|
-
"--config",
|
|
1193
|
-
help="JSON configuration string or @file reference (e.g., @config.json)",
|
|
1194
|
-
)
|
|
1195
|
-
@click.option(
|
|
1196
|
-
"--auth",
|
|
1197
|
-
"--authentication",
|
|
1198
|
-
"auth",
|
|
1199
|
-
help="JSON authentication object or @file reference (e.g., @auth.json)",
|
|
1200
|
-
)
|
|
1201
|
-
@click.option(
|
|
1202
|
-
"--import",
|
|
1203
|
-
"import_file",
|
|
1204
|
-
type=click.Path(exists=True, dir_okay=False, readable=True),
|
|
1205
|
-
help="Import MCP configuration from JSON or YAML export",
|
|
1206
|
-
)
|
|
1207
|
-
@click.option("-y", is_flag=True, help="Skip confirmation prompt when using --import")
|
|
1208
|
-
@output_flags()
|
|
1209
|
-
@click.pass_context
|
|
1210
|
-
def update(
|
|
1211
|
-
ctx: Any,
|
|
1212
|
-
mcp_ref: str,
|
|
1213
|
-
name: str | None,
|
|
1214
|
-
transport: str | None,
|
|
1215
|
-
description: str | None,
|
|
1216
|
-
config: str | None,
|
|
1217
|
-
auth: str | None,
|
|
1218
|
-
import_file: str | None,
|
|
1219
|
-
y: bool,
|
|
1220
|
-
) -> None:
|
|
1221
|
-
r"""Update an existing MCP with new configuration values.
|
|
1222
|
-
|
|
1223
|
-
You can update an MCP by providing individual fields via CLI options, or by
|
|
1224
|
-
importing from a file and optionally overriding specific fields.
|
|
1225
|
-
|
|
1226
|
-
Args:
|
|
1227
|
-
ctx: Click context containing output format preferences
|
|
1228
|
-
mcp_ref: MCP reference (ID or name)
|
|
1229
|
-
name: New MCP name (optional)
|
|
1230
|
-
transport: New transport protocol (optional)
|
|
1231
|
-
description: New description (optional)
|
|
1232
|
-
config: New JSON configuration string or @file reference (optional)
|
|
1233
|
-
auth: New JSON authentication object or @file reference (optional)
|
|
1234
|
-
import_file: Optional path to import configuration from export file.
|
|
1235
|
-
CLI options override imported values.
|
|
1236
|
-
y: Skip confirmation prompt when using --import
|
|
1237
|
-
|
|
1238
|
-
Raises:
|
|
1239
|
-
ClickException: If MCP not found, JSON invalid, or no fields specified
|
|
1240
|
-
|
|
1241
|
-
Note:
|
|
1242
|
-
Must specify either --import OR at least one CLI field.
|
|
1243
|
-
CLI options override imported values when both are specified.
|
|
1244
|
-
Uses PATCH for import-based updates, PUT/PATCH for CLI-only updates.
|
|
1245
|
-
|
|
1246
|
-
\b
|
|
1247
|
-
Examples:
|
|
1248
|
-
Update with CLI options:
|
|
1249
|
-
aip mcps update my-mcp --name new-name --transport sse
|
|
1250
|
-
|
|
1251
|
-
Import from file:
|
|
1252
|
-
aip mcps update my-mcp --import mcp-export.json
|
|
1253
|
-
|
|
1254
|
-
Import with overrides:
|
|
1255
|
-
aip mcps update my-mcp --import mcp-export.json --name new-name -y
|
|
1256
|
-
"""
|
|
1257
|
-
try:
|
|
1258
|
-
client = get_client(ctx)
|
|
1259
|
-
|
|
1260
|
-
# Validate that at least one update method is provided
|
|
1261
|
-
cli_flags_provided = any(v is not None for v in [name, transport, description, config, auth])
|
|
1262
|
-
if not import_file and not cli_flags_provided:
|
|
1263
|
-
raise click.ClickException(
|
|
1264
|
-
"No update fields specified. Use --import or one of: "
|
|
1265
|
-
"--name, --transport, --description, --config, --auth"
|
|
1266
|
-
)
|
|
1267
|
-
|
|
1268
|
-
# Resolve MCP using helper function
|
|
1269
|
-
mcp = _resolve_mcp(ctx, client, mcp_ref)
|
|
1270
|
-
|
|
1271
|
-
# Load and validate import data if provided
|
|
1272
|
-
import_payload = None
|
|
1273
|
-
if import_file:
|
|
1274
|
-
import_payload = _load_import_ready_payload(import_file)
|
|
1275
|
-
if not _validate_import_payload_fields(import_payload):
|
|
1276
|
-
return
|
|
1277
|
-
|
|
1278
|
-
# Build update data from import and CLI flags
|
|
1279
|
-
update_data = _build_update_data_from_sources(import_payload, mcp, name, transport, description, config, auth)
|
|
1280
|
-
|
|
1281
|
-
if not update_data:
|
|
1282
|
-
raise click.ClickException("No update fields specified")
|
|
1283
|
-
|
|
1284
|
-
# Show confirmation preview for import-based updates (unless -y flag)
|
|
1285
|
-
if import_payload and not y:
|
|
1286
|
-
cli_overrides = _collect_cli_overrides(name, transport, description, config, auth)
|
|
1287
|
-
preview = _generate_update_preview(mcp, update_data, cli_overrides)
|
|
1288
|
-
print_markup(preview)
|
|
1289
|
-
|
|
1290
|
-
if not click.confirm("\nContinue with update?", default=False):
|
|
1291
|
-
print_markup("[yellow]Update cancelled.[/yellow]")
|
|
1292
|
-
return
|
|
1293
|
-
|
|
1294
|
-
# Update MCP
|
|
1295
|
-
with spinner_context(
|
|
1296
|
-
ctx,
|
|
1297
|
-
"[bold blue]Updating MCP…[/bold blue]",
|
|
1298
|
-
console_override=console,
|
|
1299
|
-
):
|
|
1300
|
-
updated_mcp = client.mcps.update_mcp(mcp.id, **update_data)
|
|
1301
|
-
|
|
1302
|
-
handle_json_output(ctx, updated_mcp.model_dump())
|
|
1303
|
-
handle_rich_output(ctx, display_update_success("MCP", updated_mcp.name))
|
|
1304
|
-
|
|
1305
|
-
except Exception as e:
|
|
1306
|
-
_handle_cli_error(ctx, e, "MCP update")
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
@mcps_group.command()
|
|
1310
|
-
@click.argument("mcp_ref")
|
|
1311
|
-
@click.option("-y", "--yes", is_flag=True, help="Skip confirmation")
|
|
1312
|
-
@output_flags()
|
|
1313
|
-
@click.pass_context
|
|
1314
|
-
def delete(ctx: Any, mcp_ref: str, yes: bool) -> None:
|
|
1315
|
-
"""Delete an MCP after confirmation.
|
|
1316
|
-
|
|
1317
|
-
Args:
|
|
1318
|
-
ctx: Click context containing output format preferences
|
|
1319
|
-
mcp_ref: MCP reference (ID or name)
|
|
1320
|
-
yes: Skip confirmation prompt if True
|
|
1321
|
-
|
|
1322
|
-
Raises:
|
|
1323
|
-
ClickException: If MCP not found or deletion fails
|
|
1324
|
-
|
|
1325
|
-
Note:
|
|
1326
|
-
Requires confirmation unless --yes flag is provided.
|
|
1327
|
-
Deletion is permanent and cannot be undone.
|
|
1328
|
-
"""
|
|
1329
|
-
try:
|
|
1330
|
-
client = get_client(ctx)
|
|
1331
|
-
|
|
1332
|
-
# Resolve MCP using helper function
|
|
1333
|
-
mcp = _resolve_mcp(ctx, client, mcp_ref)
|
|
1334
|
-
|
|
1335
|
-
# Confirm deletion
|
|
1336
|
-
if not yes and not display_confirmation_prompt("MCP", mcp.name):
|
|
1337
|
-
return
|
|
1338
|
-
|
|
1339
|
-
with spinner_context(
|
|
1340
|
-
ctx,
|
|
1341
|
-
"[bold blue]Deleting MCP…[/bold blue]",
|
|
1342
|
-
console_override=console,
|
|
1343
|
-
):
|
|
1344
|
-
client.mcps.delete_mcp(mcp.id)
|
|
1345
|
-
|
|
1346
|
-
handle_json_output(
|
|
1347
|
-
ctx,
|
|
1348
|
-
{
|
|
1349
|
-
"success": True,
|
|
1350
|
-
"message": f"MCP '{mcp.name}' deleted",
|
|
1351
|
-
},
|
|
1352
|
-
)
|
|
1353
|
-
handle_rich_output(ctx, display_deletion_success("MCP", mcp.name))
|
|
1354
|
-
|
|
1355
|
-
except Exception as e:
|
|
1356
|
-
_handle_cli_error(ctx, e, "MCP deletion")
|