glaip-sdk 0.6.19__py3-none-any.whl → 0.7.27__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/agents/base.py +283 -30
- glaip_sdk/agents/component.py +233 -0
- 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 +116 -0
- glaip_sdk/cli/commands/agents/_common.py +562 -0
- glaip_sdk/cli/commands/agents/create.py +155 -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 +1 -1
- glaip_sdk/cli/commands/configure.py +1 -2
- 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/entrypoint.py +20 -0
- glaip_sdk/cli/main.py +112 -35
- glaip_sdk/cli/pager.py +3 -3
- glaip_sdk/cli/resolution.py +2 -1
- glaip_sdk/cli/slash/accounts_controller.py +3 -1
- glaip_sdk/cli/slash/agent_session.py +1 -1
- glaip_sdk/cli/slash/remote_runs_controller.py +3 -1
- glaip_sdk/cli/slash/session.py +343 -20
- glaip_sdk/cli/slash/tui/__init__.py +29 -1
- glaip_sdk/cli/slash/tui/accounts.tcss +97 -6
- glaip_sdk/cli/slash/tui/accounts_app.py +1117 -126
- glaip_sdk/cli/slash/tui/clipboard.py +316 -0
- glaip_sdk/cli/slash/tui/context.py +92 -0
- glaip_sdk/cli/slash/tui/indicators.py +341 -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 +184 -0
- glaip_sdk/cli/slash/tui/loading.py +43 -21
- glaip_sdk/cli/slash/tui/remote_runs_app.py +178 -20
- 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 +388 -0
- glaip_sdk/cli/transcript/history.py +1 -1
- glaip_sdk/cli/transcript/viewer.py +1 -1
- 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 +293 -17
- glaip_sdk/client/base.py +25 -0
- glaip_sdk/client/hitl.py +136 -0
- glaip_sdk/client/main.py +7 -5
- 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} +28 -48
- glaip_sdk/client/payloads/agent/responses.py +43 -0
- glaip_sdk/client/run_rendering.py +109 -30
- glaip_sdk/client/schedules.py +439 -0
- glaip_sdk/client/tools.py +52 -23
- glaip_sdk/config/constants.py +22 -2
- glaip_sdk/guardrails/__init__.py +80 -0
- glaip_sdk/guardrails/serializer.py +91 -0
- glaip_sdk/hitl/__init__.py +35 -2
- glaip_sdk/hitl/base.py +64 -0
- glaip_sdk/hitl/callback.py +43 -0
- glaip_sdk/hitl/local.py +1 -31
- glaip_sdk/hitl/remote.py +523 -0
- glaip_sdk/models/__init__.py +47 -1
- glaip_sdk/models/_provider_mappings.py +101 -0
- glaip_sdk/models/_validation.py +97 -0
- glaip_sdk/models/agent.py +2 -1
- glaip_sdk/models/agent_runs.py +2 -1
- glaip_sdk/models/constants.py +141 -0
- glaip_sdk/models/model.py +170 -0
- 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/ptc.py +145 -0
- glaip_sdk/registry/tool.py +270 -57
- glaip_sdk/runner/__init__.py +20 -3
- glaip_sdk/runner/deps.py +4 -1
- glaip_sdk/runner/langgraph.py +251 -27
- glaip_sdk/runner/logging_config.py +77 -0
- glaip_sdk/runner/mcp_adapter/mcp_config_builder.py +30 -9
- glaip_sdk/runner/ptc_adapter.py +98 -0
- glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +25 -2
- 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/agent_config.py +8 -2
- glaip_sdk/utils/bundler.py +138 -2
- glaip_sdk/utils/import_resolver.py +427 -49
- glaip_sdk/utils/runtime_config.py +3 -2
- glaip_sdk/utils/sync.py +31 -11
- glaip_sdk/utils/tool_detection.py +274 -6
- {glaip_sdk-0.6.19.dist-info → glaip_sdk-0.7.27.dist-info}/METADATA +22 -8
- glaip_sdk-0.7.27.dist-info/RECORD +227 -0
- {glaip_sdk-0.6.19.dist-info → glaip_sdk-0.7.27.dist-info}/WHEEL +1 -1
- glaip_sdk-0.7.27.dist-info/entry_points.txt +2 -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.19.dist-info/RECORD +0 -163
- glaip_sdk-0.6.19.dist-info/entry_points.txt +0 -2
- {glaip_sdk-0.6.19.dist-info → glaip_sdk-0.7.27.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
"""Common helpers and group definition for MCP commands.
|
|
2
|
+
|
|
3
|
+
Authors:
|
|
4
|
+
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
import click
|
|
13
|
+
from rich.console import Console
|
|
14
|
+
|
|
15
|
+
from glaip_sdk.cli.context import get_ctx_value
|
|
16
|
+
from glaip_sdk.cli.display import display_api_error, handle_json_output
|
|
17
|
+
from glaip_sdk.cli.io import load_resource_from_file_with_validation
|
|
18
|
+
from glaip_sdk.cli.mcp_validators import validate_mcp_auth_structure, validate_mcp_config_structure
|
|
19
|
+
from glaip_sdk.cli.parsers.json_input import parse_json_input
|
|
20
|
+
from glaip_sdk.cli.resolution import resolve_resource_reference
|
|
21
|
+
from glaip_sdk.cli.rich_helpers import print_markup
|
|
22
|
+
from glaip_sdk.cli.commands.shared.formatters import _format_empty_override_warnings, _format_preview_value
|
|
23
|
+
from glaip_sdk.utils.import_export import convert_export_to_import_format
|
|
24
|
+
|
|
25
|
+
console = Console()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@click.group(name="mcps", no_args_is_help=True)
|
|
29
|
+
def mcps_group() -> None:
|
|
30
|
+
"""MCP management operations.
|
|
31
|
+
|
|
32
|
+
Provides commands for creating, listing, updating, deleting, and managing
|
|
33
|
+
Model Context Protocol (MCP) configurations.
|
|
34
|
+
"""
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _resolve_mcp(ctx: Any, client: Any, ref: str, select: int | None = None) -> Any | None:
|
|
39
|
+
"""Resolve an MCP server by ID or name, with interactive selection support.
|
|
40
|
+
|
|
41
|
+
This function provides MCP-specific resolution logic. It delegates to
|
|
42
|
+
resolve_resource_reference for MCP-specific resolution, supporting UUID
|
|
43
|
+
lookups and name-based fuzzy matching.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
ctx: Click context for command execution.
|
|
47
|
+
client: API client for backend operations.
|
|
48
|
+
ref: MCP identifier (UUID or name string).
|
|
49
|
+
select: Optional selection index when multiple MCPs match (1-based).
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
MCP instance if resolution succeeds, None if not found.
|
|
53
|
+
|
|
54
|
+
Raises:
|
|
55
|
+
click.ClickException: When resolution fails or selection is invalid.
|
|
56
|
+
"""
|
|
57
|
+
# Configure MCP-specific resolution functions
|
|
58
|
+
mcp_client = client.mcps
|
|
59
|
+
get_by_id_func = mcp_client.get_mcp_by_id
|
|
60
|
+
find_by_name_func = mcp_client.find_mcps
|
|
61
|
+
# Use MCP-specific resolution with standard fuzzy matching
|
|
62
|
+
return resolve_resource_reference(
|
|
63
|
+
ctx,
|
|
64
|
+
client,
|
|
65
|
+
ref,
|
|
66
|
+
"mcp",
|
|
67
|
+
get_by_id_func,
|
|
68
|
+
find_by_name_func,
|
|
69
|
+
"MCP",
|
|
70
|
+
select=select,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _strip_server_only_fields(import_data: dict[str, Any]) -> dict[str, Any]:
|
|
75
|
+
"""Remove fields that should not be forwarded during import-driven creation.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
import_data: Raw import payload loaded from disk.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
A shallow copy of the data with server-managed fields removed.
|
|
82
|
+
"""
|
|
83
|
+
cleaned = dict(import_data)
|
|
84
|
+
for key in (
|
|
85
|
+
"id",
|
|
86
|
+
"type",
|
|
87
|
+
"status",
|
|
88
|
+
"connection_status",
|
|
89
|
+
"created_at",
|
|
90
|
+
"updated_at",
|
|
91
|
+
):
|
|
92
|
+
cleaned.pop(key, None)
|
|
93
|
+
return cleaned
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _load_import_ready_payload(import_file: str) -> dict[str, Any]:
|
|
97
|
+
"""Load and normalise an imported MCP definition for create operations.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
import_file: Path to an MCP export file (JSON or YAML).
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
Normalised import payload ready for CLI/REST usage.
|
|
104
|
+
|
|
105
|
+
Raises:
|
|
106
|
+
click.ClickException: If the file cannot be parsed or validated.
|
|
107
|
+
"""
|
|
108
|
+
raw_data = load_resource_from_file_with_validation(Path(import_file), "MCP")
|
|
109
|
+
import_data = convert_export_to_import_format(raw_data)
|
|
110
|
+
import_data = _strip_server_only_fields(import_data)
|
|
111
|
+
|
|
112
|
+
transport = import_data.get("transport")
|
|
113
|
+
|
|
114
|
+
if "config" in import_data:
|
|
115
|
+
import_data["config"] = validate_mcp_config_structure(
|
|
116
|
+
import_data["config"],
|
|
117
|
+
transport=transport,
|
|
118
|
+
source="import file",
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
if "authentication" in import_data:
|
|
122
|
+
import_data["authentication"] = validate_mcp_auth_structure(
|
|
123
|
+
import_data["authentication"],
|
|
124
|
+
source="import file",
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
return import_data
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _coerce_cli_string(value: str | None) -> str | None:
|
|
131
|
+
"""Normalise CLI string values so blanks are treated as missing.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
value: User-provided string option.
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
The stripped string, or ``None`` when the value is blank/whitespace-only.
|
|
138
|
+
"""
|
|
139
|
+
if value is None:
|
|
140
|
+
return None
|
|
141
|
+
trimmed = value.strip()
|
|
142
|
+
# Treat whitespace-only strings as None
|
|
143
|
+
return trimmed if trimmed else None
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _merge_config_field(
|
|
147
|
+
merged_base: dict[str, Any],
|
|
148
|
+
cli_config: str | None,
|
|
149
|
+
final_transport: str | None,
|
|
150
|
+
) -> None:
|
|
151
|
+
"""Merge config field with validation.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
merged_base: Base payload to update in-place.
|
|
155
|
+
cli_config: Raw CLI JSON string for config.
|
|
156
|
+
final_transport: Transport type for validation.
|
|
157
|
+
|
|
158
|
+
Raises:
|
|
159
|
+
click.ClickException: If config JSON parsing or validation fails.
|
|
160
|
+
"""
|
|
161
|
+
if cli_config is not None:
|
|
162
|
+
parsed_config = parse_json_input(cli_config)
|
|
163
|
+
merged_base["config"] = validate_mcp_config_structure(
|
|
164
|
+
parsed_config,
|
|
165
|
+
transport=final_transport,
|
|
166
|
+
source="--config",
|
|
167
|
+
)
|
|
168
|
+
elif "config" not in merged_base or merged_base["config"] is None:
|
|
169
|
+
merged_base["config"] = {}
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _merge_auth_field(
|
|
173
|
+
merged_base: dict[str, Any],
|
|
174
|
+
cli_auth: str | None,
|
|
175
|
+
) -> None:
|
|
176
|
+
"""Merge authentication field with validation.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
merged_base: Base payload to update in-place.
|
|
180
|
+
cli_auth: Raw CLI JSON string for authentication.
|
|
181
|
+
|
|
182
|
+
Raises:
|
|
183
|
+
click.ClickException: If auth JSON parsing or validation fails.
|
|
184
|
+
"""
|
|
185
|
+
if cli_auth is not None:
|
|
186
|
+
parsed_auth = parse_json_input(cli_auth)
|
|
187
|
+
merged_base["authentication"] = validate_mcp_auth_structure(
|
|
188
|
+
parsed_auth,
|
|
189
|
+
source="--auth",
|
|
190
|
+
)
|
|
191
|
+
elif "authentication" not in merged_base:
|
|
192
|
+
merged_base["authentication"] = None
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def _merge_import_payload(
|
|
196
|
+
import_data: dict[str, Any] | None,
|
|
197
|
+
*,
|
|
198
|
+
cli_name: str | None,
|
|
199
|
+
cli_transport: str | None,
|
|
200
|
+
cli_description: str | None,
|
|
201
|
+
cli_config: str | None,
|
|
202
|
+
cli_auth: str | None,
|
|
203
|
+
) -> tuple[dict[str, Any], list[str]]:
|
|
204
|
+
"""Merge import data with CLI overrides while tracking missing fields.
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
import_data: Normalised payload loaded from file (if provided).
|
|
208
|
+
cli_name: Name supplied via CLI option.
|
|
209
|
+
cli_transport: Transport supplied via CLI option.
|
|
210
|
+
cli_description: Description supplied via CLI option.
|
|
211
|
+
cli_config: Raw CLI JSON string for config.
|
|
212
|
+
cli_auth: Raw CLI JSON string for authentication.
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
A tuple of (merged_payload, missing_required_fields).
|
|
216
|
+
|
|
217
|
+
Raises:
|
|
218
|
+
click.ClickException: If config/auth JSON parsing or validation fails.
|
|
219
|
+
"""
|
|
220
|
+
merged_base = import_data.copy() if import_data else {}
|
|
221
|
+
|
|
222
|
+
# Merge simple string fields using truthy CLI overrides
|
|
223
|
+
for field, cli_value in (
|
|
224
|
+
("name", _coerce_cli_string(cli_name)),
|
|
225
|
+
("transport", _coerce_cli_string(cli_transport)),
|
|
226
|
+
("description", _coerce_cli_string(cli_description)),
|
|
227
|
+
):
|
|
228
|
+
if cli_value is not None:
|
|
229
|
+
merged_base[field] = cli_value
|
|
230
|
+
|
|
231
|
+
# Determine final transport before validating config
|
|
232
|
+
final_transport = merged_base.get("transport")
|
|
233
|
+
|
|
234
|
+
# Merge config and authentication with validation
|
|
235
|
+
_merge_config_field(merged_base, cli_config, final_transport)
|
|
236
|
+
_merge_auth_field(merged_base, cli_auth)
|
|
237
|
+
|
|
238
|
+
# Validate required fields
|
|
239
|
+
missing_fields = []
|
|
240
|
+
for required in ("name", "transport"):
|
|
241
|
+
value = merged_base.get(required)
|
|
242
|
+
if not isinstance(value, str) or not value.strip():
|
|
243
|
+
missing_fields.append(required)
|
|
244
|
+
|
|
245
|
+
return merged_base, missing_fields
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def _validate_import_payload_fields(import_payload: dict[str, Any]) -> bool:
|
|
249
|
+
"""Validate that import payload contains updatable fields.
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
import_payload: Import payload to validate
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
True if payload has updatable fields, False otherwise
|
|
256
|
+
"""
|
|
257
|
+
updatable_fields = {"name", "transport", "description", "config", "authentication"}
|
|
258
|
+
has_updatable = any(field in import_payload for field in updatable_fields)
|
|
259
|
+
|
|
260
|
+
if not has_updatable:
|
|
261
|
+
available_fields = set(import_payload.keys())
|
|
262
|
+
print_markup(
|
|
263
|
+
"[yellow]⚠️ No updatable fields found in import file.[/yellow]\n"
|
|
264
|
+
f"[dim]Found fields: {', '.join(sorted(available_fields))}[/dim]\n"
|
|
265
|
+
f"[dim]Updatable fields: {', '.join(sorted(updatable_fields))}[/dim]"
|
|
266
|
+
)
|
|
267
|
+
return has_updatable
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def _get_config_transport(
|
|
271
|
+
transport: str | None,
|
|
272
|
+
import_payload: dict[str, Any] | None,
|
|
273
|
+
mcp: Any,
|
|
274
|
+
) -> str | None:
|
|
275
|
+
"""Get the transport value for config validation.
|
|
276
|
+
|
|
277
|
+
Args:
|
|
278
|
+
transport: CLI transport flag
|
|
279
|
+
import_payload: Optional import payload
|
|
280
|
+
mcp: Current MCP object
|
|
281
|
+
|
|
282
|
+
Returns:
|
|
283
|
+
Transport value or None
|
|
284
|
+
"""
|
|
285
|
+
if import_payload:
|
|
286
|
+
return transport or import_payload.get("transport")
|
|
287
|
+
return transport or getattr(mcp, "transport", None)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def _collect_cli_overrides(
|
|
291
|
+
name: str | None,
|
|
292
|
+
transport: str | None,
|
|
293
|
+
description: str | None,
|
|
294
|
+
config: str | None,
|
|
295
|
+
auth: str | None,
|
|
296
|
+
) -> dict[str, Any]:
|
|
297
|
+
"""Collect CLI flags that were explicitly provided.
|
|
298
|
+
|
|
299
|
+
Args:
|
|
300
|
+
name: CLI name flag
|
|
301
|
+
transport: CLI transport flag
|
|
302
|
+
description: CLI description flag
|
|
303
|
+
config: CLI config flag
|
|
304
|
+
auth: CLI auth flag
|
|
305
|
+
|
|
306
|
+
Returns:
|
|
307
|
+
Dictionary of provided CLI overrides
|
|
308
|
+
"""
|
|
309
|
+
cli_overrides = {}
|
|
310
|
+
if name is not None:
|
|
311
|
+
cli_overrides["name"] = name
|
|
312
|
+
if transport is not None:
|
|
313
|
+
cli_overrides["transport"] = transport
|
|
314
|
+
if description is not None:
|
|
315
|
+
cli_overrides["description"] = description
|
|
316
|
+
if config is not None:
|
|
317
|
+
cli_overrides["config"] = config
|
|
318
|
+
if auth is not None:
|
|
319
|
+
cli_overrides["auth"] = auth
|
|
320
|
+
return cli_overrides
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def _handle_cli_error(ctx: Any, error: Exception, operation: str) -> None:
|
|
324
|
+
"""Render CLI error once and exit with non-zero status."""
|
|
325
|
+
handle_json_output(ctx, error=error)
|
|
326
|
+
if get_ctx_value(ctx, "view") != "json":
|
|
327
|
+
display_api_error(error, operation)
|
|
328
|
+
ctx.exit(1)
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def _parse_and_validate_config_auth(
|
|
332
|
+
update_dict: dict[str, Any],
|
|
333
|
+
config: str | None,
|
|
334
|
+
auth: str | None,
|
|
335
|
+
transport: str | None,
|
|
336
|
+
import_payload: dict[str, Any] | None,
|
|
337
|
+
mcp: Any,
|
|
338
|
+
) -> None:
|
|
339
|
+
"""Parse and validate config and auth CLI options, updating dict in-place.
|
|
340
|
+
|
|
341
|
+
Args:
|
|
342
|
+
update_dict: Dictionary to update with parsed config/auth
|
|
343
|
+
config: Config option string
|
|
344
|
+
auth: Auth option string
|
|
345
|
+
transport: Transport option for config validation
|
|
346
|
+
import_payload: Import payload dictionary or None
|
|
347
|
+
mcp: Current MCP object
|
|
348
|
+
"""
|
|
349
|
+
if config is not None:
|
|
350
|
+
parsed_config = parse_json_input(config)
|
|
351
|
+
config_transport = _get_config_transport(transport, import_payload, mcp)
|
|
352
|
+
update_dict["config"] = validate_mcp_config_structure(
|
|
353
|
+
parsed_config,
|
|
354
|
+
transport=config_transport,
|
|
355
|
+
source="--config",
|
|
356
|
+
)
|
|
357
|
+
if auth is not None:
|
|
358
|
+
parsed_auth = parse_json_input(auth)
|
|
359
|
+
update_dict["authentication"] = validate_mcp_auth_structure(parsed_auth, source="--auth")
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def _generate_update_preview(mcp: Any, update_data: dict[str, Any], cli_overrides: dict[str, Any]) -> str:
|
|
363
|
+
"""Generate formatted preview of changes for user confirmation.
|
|
364
|
+
|
|
365
|
+
Args:
|
|
366
|
+
mcp: Current MCP object
|
|
367
|
+
update_data: Data that will be sent in update request
|
|
368
|
+
cli_overrides: CLI flags that were explicitly provided
|
|
369
|
+
|
|
370
|
+
Returns:
|
|
371
|
+
Formatted preview string showing old→new values
|
|
372
|
+
"""
|
|
373
|
+
lines = [f"\n[bold]The following fields will be updated for MCP '{mcp.name}':[/bold]\n"]
|
|
374
|
+
|
|
375
|
+
empty_overrides = []
|
|
376
|
+
|
|
377
|
+
# Show each field that will be updated
|
|
378
|
+
for field, new_value in update_data.items():
|
|
379
|
+
old_value = getattr(mcp, field, None)
|
|
380
|
+
|
|
381
|
+
# Track empty CLI overrides
|
|
382
|
+
if field in cli_overrides and cli_overrides[field] == "":
|
|
383
|
+
empty_overrides.append(field)
|
|
384
|
+
|
|
385
|
+
old_display = _format_preview_value(old_value)
|
|
386
|
+
new_display = _format_preview_value(new_value)
|
|
387
|
+
|
|
388
|
+
lines.append(f"- [cyan]{field}[/cyan]: {old_display} → {new_display}")
|
|
389
|
+
|
|
390
|
+
# Add warnings for empty CLI overrides
|
|
391
|
+
lines.extend(_format_empty_override_warnings(empty_overrides))
|
|
392
|
+
|
|
393
|
+
return "\n".join(lines)
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
def _validate_update_inputs(
|
|
397
|
+
name: str | None,
|
|
398
|
+
transport: str | None,
|
|
399
|
+
description: str | None,
|
|
400
|
+
config: str | None,
|
|
401
|
+
auth: str | None,
|
|
402
|
+
import_file: str | None,
|
|
403
|
+
) -> None:
|
|
404
|
+
"""Validate that at least one update method is provided.
|
|
405
|
+
|
|
406
|
+
Args:
|
|
407
|
+
name: MCP name option
|
|
408
|
+
transport: Transport option
|
|
409
|
+
description: Description option
|
|
410
|
+
config: Config option
|
|
411
|
+
auth: Auth option
|
|
412
|
+
import_file: Import file option
|
|
413
|
+
|
|
414
|
+
Raises:
|
|
415
|
+
ClickException: If no update fields are specified
|
|
416
|
+
"""
|
|
417
|
+
cli_flags_provided = any(v is not None for v in [name, transport, description, config, auth])
|
|
418
|
+
if not import_file and not cli_flags_provided:
|
|
419
|
+
raise click.ClickException(
|
|
420
|
+
"No update fields specified. Use --import or one of: --name, --transport, --description, --config, --auth"
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def _handle_update_preview_and_confirmation(
|
|
425
|
+
import_payload: dict[str, Any] | None,
|
|
426
|
+
y: bool,
|
|
427
|
+
mcp: Any,
|
|
428
|
+
update_data: dict[str, Any],
|
|
429
|
+
name: str | None,
|
|
430
|
+
transport: str | None,
|
|
431
|
+
description: str | None,
|
|
432
|
+
config: str | None,
|
|
433
|
+
auth: str | None,
|
|
434
|
+
) -> bool:
|
|
435
|
+
"""Handle preview display and user confirmation for import-based updates.
|
|
436
|
+
|
|
437
|
+
Args:
|
|
438
|
+
import_payload: Import payload dictionary or None
|
|
439
|
+
y: Skip confirmation flag
|
|
440
|
+
mcp: Current MCP object
|
|
441
|
+
update_data: Data that will be sent in update request
|
|
442
|
+
name: MCP name option
|
|
443
|
+
transport: Transport option
|
|
444
|
+
description: Description option
|
|
445
|
+
config: Config option
|
|
446
|
+
auth: Auth option
|
|
447
|
+
|
|
448
|
+
Returns:
|
|
449
|
+
True if update should proceed, False if cancelled
|
|
450
|
+
"""
|
|
451
|
+
if import_payload and not y:
|
|
452
|
+
cli_overrides = _collect_cli_overrides(name, transport, description, config, auth)
|
|
453
|
+
preview = _generate_update_preview(mcp, update_data, cli_overrides)
|
|
454
|
+
print_markup(preview)
|
|
455
|
+
|
|
456
|
+
if not click.confirm("\nContinue with update?", default=False):
|
|
457
|
+
print_markup("[yellow]Update cancelled.[/yellow]")
|
|
458
|
+
return False
|
|
459
|
+
return True
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""Connect to MCP command.
|
|
2
|
+
|
|
3
|
+
Authors:
|
|
4
|
+
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
import click
|
|
13
|
+
|
|
14
|
+
from glaip_sdk.branding import SUCCESS, SUCCESS_STYLE, WARNING_STYLE
|
|
15
|
+
from glaip_sdk.cli.context import get_ctx_value, output_flags
|
|
16
|
+
from glaip_sdk.cli.core.context import get_client
|
|
17
|
+
from glaip_sdk.cli.core.rendering import spinner_context
|
|
18
|
+
from glaip_sdk.cli.display import handle_json_output
|
|
19
|
+
from glaip_sdk.cli.rich_helpers import print_markup
|
|
20
|
+
from glaip_sdk.rich_components import AIPPanel
|
|
21
|
+
|
|
22
|
+
from ._common import console, mcps_group
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@mcps_group.command("connect")
|
|
26
|
+
@click.option(
|
|
27
|
+
"--from-file",
|
|
28
|
+
"config_file",
|
|
29
|
+
required=True,
|
|
30
|
+
help="MCP config JSON file",
|
|
31
|
+
)
|
|
32
|
+
@output_flags()
|
|
33
|
+
@click.pass_context
|
|
34
|
+
def connect(ctx: Any, config_file: str) -> None:
|
|
35
|
+
"""Test MCP connection using a configuration file.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
ctx: Click context containing output format preferences
|
|
39
|
+
config_file: Path to MCP configuration JSON file
|
|
40
|
+
|
|
41
|
+
Raises:
|
|
42
|
+
ClickException: If config file invalid or connection test fails
|
|
43
|
+
|
|
44
|
+
Note:
|
|
45
|
+
Loads MCP configuration from JSON file and tests connectivity.
|
|
46
|
+
Displays success or failure with connection details.
|
|
47
|
+
"""
|
|
48
|
+
try:
|
|
49
|
+
client = get_client(ctx)
|
|
50
|
+
|
|
51
|
+
# Load MCP config from file
|
|
52
|
+
with open(config_file) as f:
|
|
53
|
+
config = json.load(f)
|
|
54
|
+
|
|
55
|
+
view = get_ctx_value(ctx, "view", "rich")
|
|
56
|
+
if view != "json":
|
|
57
|
+
print_markup(
|
|
58
|
+
f"[{WARNING_STYLE}]Connecting to MCP with config from {config_file}...[/]",
|
|
59
|
+
console=console,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# Test connection using config
|
|
63
|
+
with spinner_context(
|
|
64
|
+
ctx,
|
|
65
|
+
"[bold blue]Connecting to MCP…[/bold blue]",
|
|
66
|
+
console_override=console,
|
|
67
|
+
):
|
|
68
|
+
result = client.mcps.test_mcp_connection_from_config(config)
|
|
69
|
+
|
|
70
|
+
view = get_ctx_value(ctx, "view", "rich")
|
|
71
|
+
if view == "json":
|
|
72
|
+
handle_json_output(ctx, result)
|
|
73
|
+
else:
|
|
74
|
+
success_panel = AIPPanel(
|
|
75
|
+
f"[{SUCCESS_STYLE}]✓[/] MCP connection successful!\n\n[bold]Result:[/bold] {result}",
|
|
76
|
+
title="🔌 Connection",
|
|
77
|
+
border_style=SUCCESS,
|
|
78
|
+
)
|
|
79
|
+
console.print(success_panel)
|
|
80
|
+
|
|
81
|
+
except Exception as e:
|
|
82
|
+
raise click.ClickException(str(e)) from e
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"""Create MCP command.
|
|
2
|
+
|
|
3
|
+
Authors:
|
|
4
|
+
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
import click
|
|
12
|
+
|
|
13
|
+
from glaip_sdk.cli.context import output_flags
|
|
14
|
+
from glaip_sdk.cli.display import display_creation_success, handle_json_output, handle_rich_output
|
|
15
|
+
from glaip_sdk.cli.core.context import get_client
|
|
16
|
+
from glaip_sdk.cli.core.rendering import spinner_context
|
|
17
|
+
from glaip_sdk.config.constants import DEFAULT_MCP_TYPE
|
|
18
|
+
|
|
19
|
+
from ._common import (
|
|
20
|
+
_handle_cli_error,
|
|
21
|
+
_load_import_ready_payload,
|
|
22
|
+
_merge_import_payload,
|
|
23
|
+
console,
|
|
24
|
+
mcps_group,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@mcps_group.command()
|
|
29
|
+
@click.option("--name", help="MCP name")
|
|
30
|
+
@click.option("--transport", help="MCP transport protocol")
|
|
31
|
+
@click.option("--description", help="MCP description")
|
|
32
|
+
@click.option(
|
|
33
|
+
"--config",
|
|
34
|
+
help="JSON configuration string or @file reference (e.g., @config.json)",
|
|
35
|
+
)
|
|
36
|
+
@click.option(
|
|
37
|
+
"--auth",
|
|
38
|
+
"--authentication",
|
|
39
|
+
"auth",
|
|
40
|
+
help="JSON authentication object or @file reference (e.g., @auth.json)",
|
|
41
|
+
)
|
|
42
|
+
@click.option(
|
|
43
|
+
"--import",
|
|
44
|
+
"import_file",
|
|
45
|
+
type=click.Path(exists=True, dir_okay=False),
|
|
46
|
+
help="Import MCP configuration from JSON or YAML export",
|
|
47
|
+
)
|
|
48
|
+
@output_flags()
|
|
49
|
+
@click.pass_context
|
|
50
|
+
def create(
|
|
51
|
+
ctx: Any,
|
|
52
|
+
name: str | None,
|
|
53
|
+
transport: str | None,
|
|
54
|
+
description: str | None,
|
|
55
|
+
config: str | None,
|
|
56
|
+
auth: str | None,
|
|
57
|
+
import_file: str | None,
|
|
58
|
+
) -> None:
|
|
59
|
+
r"""Create a new MCP with specified configuration.
|
|
60
|
+
|
|
61
|
+
You can create an MCP by providing all parameters via CLI options, or by
|
|
62
|
+
importing from a file and optionally overriding specific fields.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
ctx: Click context containing output format preferences
|
|
66
|
+
name: MCP name (required unless provided via --import)
|
|
67
|
+
transport: MCP transport protocol (required unless provided via --import)
|
|
68
|
+
description: Optional MCP description
|
|
69
|
+
config: JSON configuration string or @file reference
|
|
70
|
+
auth: JSON authentication object or @file reference
|
|
71
|
+
import_file: Optional path to import configuration from export file.
|
|
72
|
+
CLI options override imported values.
|
|
73
|
+
|
|
74
|
+
Raises:
|
|
75
|
+
ClickException: If JSON parsing fails or API request fails
|
|
76
|
+
|
|
77
|
+
\b
|
|
78
|
+
Examples:
|
|
79
|
+
Create from CLI options:
|
|
80
|
+
aip mcps create --name my-mcp --transport http --config '{"url": "https://api.example.com"}'
|
|
81
|
+
|
|
82
|
+
Import from file:
|
|
83
|
+
aip mcps create --import mcp-export.json
|
|
84
|
+
|
|
85
|
+
Import with overrides:
|
|
86
|
+
aip mcps create --import mcp-export.json --name new-name --transport sse
|
|
87
|
+
"""
|
|
88
|
+
try:
|
|
89
|
+
# Get API client instance for MCP operations
|
|
90
|
+
api_client = get_client(ctx)
|
|
91
|
+
|
|
92
|
+
# Process import file if specified, otherwise use None
|
|
93
|
+
import_payload = _load_import_ready_payload(import_file) if import_file is not None else None
|
|
94
|
+
|
|
95
|
+
merged_payload, missing_fields = _merge_import_payload(
|
|
96
|
+
import_payload,
|
|
97
|
+
cli_name=name,
|
|
98
|
+
cli_transport=transport,
|
|
99
|
+
cli_description=description,
|
|
100
|
+
cli_config=config,
|
|
101
|
+
cli_auth=auth,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
if missing_fields:
|
|
105
|
+
raise click.ClickException(
|
|
106
|
+
"Missing required fields after combining import and CLI values: " + ", ".join(missing_fields)
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
effective_name = merged_payload["name"]
|
|
110
|
+
effective_transport = merged_payload["transport"]
|
|
111
|
+
effective_description = merged_payload.get("description")
|
|
112
|
+
effective_config = merged_payload.get("config") or {}
|
|
113
|
+
effective_auth = merged_payload.get("authentication")
|
|
114
|
+
mcp_metadata = merged_payload.get("mcp_metadata")
|
|
115
|
+
|
|
116
|
+
with spinner_context(
|
|
117
|
+
ctx,
|
|
118
|
+
"[bold blue]Creating MCP…[/bold blue]",
|
|
119
|
+
console_override=console,
|
|
120
|
+
):
|
|
121
|
+
# Use SDK client method to create MCP
|
|
122
|
+
create_kwargs: dict[str, Any] = {
|
|
123
|
+
"transport": effective_transport,
|
|
124
|
+
}
|
|
125
|
+
if effective_auth:
|
|
126
|
+
create_kwargs["authentication"] = effective_auth
|
|
127
|
+
if mcp_metadata is not None:
|
|
128
|
+
create_kwargs["mcp_metadata"] = mcp_metadata
|
|
129
|
+
|
|
130
|
+
mcp = api_client.mcps.create_mcp(
|
|
131
|
+
name=effective_name,
|
|
132
|
+
description=effective_description,
|
|
133
|
+
config=effective_config,
|
|
134
|
+
**create_kwargs,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
# Handle JSON output
|
|
138
|
+
handle_json_output(ctx, mcp.model_dump())
|
|
139
|
+
|
|
140
|
+
# Handle Rich output
|
|
141
|
+
rich_panel = display_creation_success(
|
|
142
|
+
"MCP",
|
|
143
|
+
mcp.name,
|
|
144
|
+
mcp.id,
|
|
145
|
+
Type=getattr(mcp, "type", DEFAULT_MCP_TYPE),
|
|
146
|
+
Transport=getattr(mcp, "transport", effective_transport),
|
|
147
|
+
Description=effective_description or "No description",
|
|
148
|
+
)
|
|
149
|
+
handle_rich_output(ctx, rich_panel)
|
|
150
|
+
|
|
151
|
+
except Exception as e:
|
|
152
|
+
_handle_cli_error(ctx, e, "MCP creation")
|