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.
Files changed (127) hide show
  1. glaip_sdk/__init__.py +42 -5
  2. glaip_sdk/agents/base.py +217 -42
  3. glaip_sdk/branding.py +113 -2
  4. glaip_sdk/cli/account_store.py +15 -0
  5. glaip_sdk/cli/auth.py +14 -8
  6. glaip_sdk/cli/commands/accounts.py +1 -1
  7. glaip_sdk/cli/commands/agents/__init__.py +119 -0
  8. glaip_sdk/cli/commands/agents/_common.py +561 -0
  9. glaip_sdk/cli/commands/agents/create.py +151 -0
  10. glaip_sdk/cli/commands/agents/delete.py +64 -0
  11. glaip_sdk/cli/commands/agents/get.py +89 -0
  12. glaip_sdk/cli/commands/agents/list.py +129 -0
  13. glaip_sdk/cli/commands/agents/run.py +264 -0
  14. glaip_sdk/cli/commands/agents/sync_langflow.py +72 -0
  15. glaip_sdk/cli/commands/agents/update.py +112 -0
  16. glaip_sdk/cli/commands/common_config.py +15 -12
  17. glaip_sdk/cli/commands/configure.py +2 -3
  18. glaip_sdk/cli/commands/mcps/__init__.py +94 -0
  19. glaip_sdk/cli/commands/mcps/_common.py +459 -0
  20. glaip_sdk/cli/commands/mcps/connect.py +82 -0
  21. glaip_sdk/cli/commands/mcps/create.py +152 -0
  22. glaip_sdk/cli/commands/mcps/delete.py +73 -0
  23. glaip_sdk/cli/commands/mcps/get.py +212 -0
  24. glaip_sdk/cli/commands/mcps/list.py +69 -0
  25. glaip_sdk/cli/commands/mcps/tools.py +235 -0
  26. glaip_sdk/cli/commands/mcps/update.py +190 -0
  27. glaip_sdk/cli/commands/models.py +2 -4
  28. glaip_sdk/cli/commands/shared/__init__.py +21 -0
  29. glaip_sdk/cli/commands/shared/formatters.py +91 -0
  30. glaip_sdk/cli/commands/tools/__init__.py +69 -0
  31. glaip_sdk/cli/commands/tools/_common.py +80 -0
  32. glaip_sdk/cli/commands/tools/create.py +228 -0
  33. glaip_sdk/cli/commands/tools/delete.py +61 -0
  34. glaip_sdk/cli/commands/tools/get.py +103 -0
  35. glaip_sdk/cli/commands/tools/list.py +69 -0
  36. glaip_sdk/cli/commands/tools/script.py +49 -0
  37. glaip_sdk/cli/commands/tools/update.py +102 -0
  38. glaip_sdk/cli/commands/transcripts/__init__.py +90 -0
  39. glaip_sdk/cli/commands/transcripts/_common.py +9 -0
  40. glaip_sdk/cli/commands/transcripts/clear.py +5 -0
  41. glaip_sdk/cli/commands/transcripts/detail.py +5 -0
  42. glaip_sdk/cli/commands/{transcripts.py → transcripts_original.py} +2 -1
  43. glaip_sdk/cli/commands/update.py +163 -17
  44. glaip_sdk/cli/config.py +1 -0
  45. glaip_sdk/cli/core/output.py +12 -7
  46. glaip_sdk/cli/entrypoint.py +20 -0
  47. glaip_sdk/cli/main.py +127 -39
  48. glaip_sdk/cli/pager.py +3 -3
  49. glaip_sdk/cli/resolution.py +2 -1
  50. glaip_sdk/cli/slash/accounts_controller.py +112 -32
  51. glaip_sdk/cli/slash/agent_session.py +5 -2
  52. glaip_sdk/cli/slash/prompt.py +11 -0
  53. glaip_sdk/cli/slash/remote_runs_controller.py +3 -1
  54. glaip_sdk/cli/slash/session.py +369 -23
  55. glaip_sdk/cli/slash/tui/__init__.py +26 -1
  56. glaip_sdk/cli/slash/tui/accounts.tcss +79 -5
  57. glaip_sdk/cli/slash/tui/accounts_app.py +1027 -88
  58. glaip_sdk/cli/slash/tui/clipboard.py +195 -0
  59. glaip_sdk/cli/slash/tui/context.py +87 -0
  60. glaip_sdk/cli/slash/tui/keybind_registry.py +235 -0
  61. glaip_sdk/cli/slash/tui/layouts/__init__.py +14 -0
  62. glaip_sdk/cli/slash/tui/layouts/harlequin.py +160 -0
  63. glaip_sdk/cli/slash/tui/remote_runs_app.py +119 -12
  64. glaip_sdk/cli/slash/tui/terminal.py +407 -0
  65. glaip_sdk/cli/slash/tui/theme/__init__.py +15 -0
  66. glaip_sdk/cli/slash/tui/theme/catalog.py +79 -0
  67. glaip_sdk/cli/slash/tui/theme/manager.py +112 -0
  68. glaip_sdk/cli/slash/tui/theme/tokens.py +55 -0
  69. glaip_sdk/cli/slash/tui/toast.py +374 -0
  70. glaip_sdk/cli/transcript/history.py +1 -1
  71. glaip_sdk/cli/transcript/viewer.py +5 -3
  72. glaip_sdk/cli/tui_settings.py +125 -0
  73. glaip_sdk/cli/update_notifier.py +215 -7
  74. glaip_sdk/cli/validators.py +1 -1
  75. glaip_sdk/client/__init__.py +2 -1
  76. glaip_sdk/client/_schedule_payloads.py +89 -0
  77. glaip_sdk/client/agents.py +50 -8
  78. glaip_sdk/client/hitl.py +136 -0
  79. glaip_sdk/client/main.py +7 -1
  80. glaip_sdk/client/mcps.py +44 -13
  81. glaip_sdk/client/payloads/agent/__init__.py +23 -0
  82. glaip_sdk/client/{_agent_payloads.py → payloads/agent/requests.py} +22 -47
  83. glaip_sdk/client/payloads/agent/responses.py +43 -0
  84. glaip_sdk/client/run_rendering.py +414 -3
  85. glaip_sdk/client/schedules.py +439 -0
  86. glaip_sdk/client/tools.py +57 -26
  87. glaip_sdk/guardrails/__init__.py +80 -0
  88. glaip_sdk/guardrails/serializer.py +89 -0
  89. glaip_sdk/hitl/__init__.py +48 -0
  90. glaip_sdk/hitl/base.py +64 -0
  91. glaip_sdk/hitl/callback.py +43 -0
  92. glaip_sdk/hitl/local.py +121 -0
  93. glaip_sdk/hitl/remote.py +523 -0
  94. glaip_sdk/models/__init__.py +17 -0
  95. glaip_sdk/models/agent_runs.py +2 -1
  96. glaip_sdk/models/schedule.py +224 -0
  97. glaip_sdk/payload_schemas/agent.py +1 -0
  98. glaip_sdk/payload_schemas/guardrails.py +34 -0
  99. glaip_sdk/registry/tool.py +273 -59
  100. glaip_sdk/runner/__init__.py +20 -3
  101. glaip_sdk/runner/deps.py +5 -8
  102. glaip_sdk/runner/langgraph.py +318 -42
  103. glaip_sdk/runner/logging_config.py +77 -0
  104. glaip_sdk/runner/mcp_adapter/langchain_mcp_adapter.py +104 -5
  105. glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +72 -7
  106. glaip_sdk/schedules/__init__.py +22 -0
  107. glaip_sdk/schedules/base.py +291 -0
  108. glaip_sdk/tools/base.py +67 -14
  109. glaip_sdk/utils/__init__.py +1 -0
  110. glaip_sdk/utils/bundler.py +138 -2
  111. glaip_sdk/utils/import_resolver.py +43 -11
  112. glaip_sdk/utils/rendering/renderer/base.py +58 -0
  113. glaip_sdk/utils/runtime_config.py +15 -12
  114. glaip_sdk/utils/sync.py +31 -11
  115. glaip_sdk/utils/tool_detection.py +274 -6
  116. glaip_sdk/utils/tool_storage_provider.py +140 -0
  117. {glaip_sdk-0.6.5b6.dist-info → glaip_sdk-0.7.12.dist-info}/METADATA +49 -37
  118. glaip_sdk-0.7.12.dist-info/RECORD +219 -0
  119. {glaip_sdk-0.6.5b6.dist-info → glaip_sdk-0.7.12.dist-info}/WHEEL +2 -1
  120. glaip_sdk-0.7.12.dist-info/entry_points.txt +2 -0
  121. glaip_sdk-0.7.12.dist-info/top_level.txt +1 -0
  122. glaip_sdk/cli/commands/agents.py +0 -1509
  123. glaip_sdk/cli/commands/mcps.py +0 -1356
  124. glaip_sdk/cli/commands/tools.py +0 -576
  125. glaip_sdk/cli/utils.py +0 -263
  126. glaip_sdk-0.6.5b6.dist-info/RECORD +0 -159
  127. glaip_sdk-0.6.5b6.dist-info/entry_points.txt +0 -3
@@ -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")