glaip-sdk 0.0.10__py3-none-any.whl → 0.0.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/cli/auth.py +414 -0
- glaip_sdk/cli/commands/agents.py +25 -3
- glaip_sdk/cli/commands/mcps.py +356 -113
- glaip_sdk/cli/main.py +8 -0
- glaip_sdk/cli/mcp_validators.py +297 -0
- glaip_sdk/cli/parsers/__init__.py +9 -0
- glaip_sdk/cli/parsers/json_input.py +140 -0
- glaip_sdk/cli/slash/session.py +24 -9
- glaip_sdk/cli/update_notifier.py +107 -0
- glaip_sdk/cli/utils.py +23 -8
- glaip_sdk/client/mcps.py +3 -3
- glaip_sdk/models.py +0 -2
- glaip_sdk/utils/import_export.py +3 -7
- glaip_sdk/utils/serialization.py +93 -2
- {glaip_sdk-0.0.10.dist-info → glaip_sdk-0.0.12.dist-info}/METADATA +2 -1
- {glaip_sdk-0.0.10.dist-info → glaip_sdk-0.0.12.dist-info}/RECORD +18 -13
- {glaip_sdk-0.0.10.dist-info → glaip_sdk-0.0.12.dist-info}/WHEEL +0 -0
- {glaip_sdk-0.0.10.dist-info → glaip_sdk-0.0.12.dist-info}/entry_points.txt +0 -0
glaip_sdk/cli/commands/mcps.py
CHANGED
|
@@ -5,6 +5,7 @@ Authors:
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import json
|
|
8
|
+
import sys
|
|
8
9
|
from pathlib import Path
|
|
9
10
|
from typing import Any
|
|
10
11
|
|
|
@@ -21,12 +22,14 @@ from glaip_sdk.cli.display import (
|
|
|
21
22
|
handle_json_output,
|
|
22
23
|
handle_rich_output,
|
|
23
24
|
)
|
|
24
|
-
from glaip_sdk.cli.io import (
|
|
25
|
-
export_resource_to_file_with_validation as export_resource_to_file,
|
|
26
|
-
)
|
|
27
25
|
from glaip_sdk.cli.io import (
|
|
28
26
|
fetch_raw_resource_details,
|
|
29
27
|
)
|
|
28
|
+
from glaip_sdk.cli.mcp_validators import (
|
|
29
|
+
validate_mcp_auth_structure,
|
|
30
|
+
validate_mcp_config_structure,
|
|
31
|
+
)
|
|
32
|
+
from glaip_sdk.cli.parsers.json_input import parse_json_input
|
|
30
33
|
from glaip_sdk.cli.resolution import resolve_resource_reference
|
|
31
34
|
from glaip_sdk.cli.utils import (
|
|
32
35
|
coerce_to_row,
|
|
@@ -40,20 +43,41 @@ from glaip_sdk.cli.utils import (
|
|
|
40
43
|
)
|
|
41
44
|
from glaip_sdk.rich_components import AIPPanel
|
|
42
45
|
from glaip_sdk.utils import format_datetime
|
|
46
|
+
from glaip_sdk.utils.serialization import (
|
|
47
|
+
build_mcp_export_payload,
|
|
48
|
+
write_resource_export,
|
|
49
|
+
)
|
|
43
50
|
|
|
44
51
|
console = Console()
|
|
45
52
|
|
|
46
53
|
|
|
47
54
|
@click.group(name="mcps", no_args_is_help=True)
|
|
48
55
|
def mcps_group() -> None:
|
|
49
|
-
"""MCP management operations.
|
|
56
|
+
"""MCP management operations.
|
|
57
|
+
|
|
58
|
+
Provides commands for creating, listing, updating, deleting, and managing
|
|
59
|
+
Model Context Protocol (MCP) configurations.
|
|
60
|
+
"""
|
|
50
61
|
pass
|
|
51
62
|
|
|
52
63
|
|
|
53
64
|
def _resolve_mcp(
|
|
54
65
|
ctx: Any, client: Any, ref: str, select: int | None = None
|
|
55
66
|
) -> Any | None:
|
|
56
|
-
"""Resolve MCP reference (ID or name) with ambiguity handling.
|
|
67
|
+
"""Resolve MCP reference (ID or name) with ambiguity handling.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
ctx: Click context object
|
|
71
|
+
client: API client instance
|
|
72
|
+
ref: MCP reference (ID or name)
|
|
73
|
+
select: Index to select when multiple matches found
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
MCP object if found, None otherwise
|
|
77
|
+
|
|
78
|
+
Raises:
|
|
79
|
+
ClickException: If MCP not found or selection invalid
|
|
80
|
+
"""
|
|
57
81
|
return resolve_resource_reference(
|
|
58
82
|
ctx,
|
|
59
83
|
client,
|
|
@@ -70,7 +94,14 @@ def _resolve_mcp(
|
|
|
70
94
|
@output_flags()
|
|
71
95
|
@click.pass_context
|
|
72
96
|
def list_mcps(ctx: Any) -> None:
|
|
73
|
-
"""List all MCPs.
|
|
97
|
+
"""List all MCPs in a formatted table.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
ctx: Click context containing output format preferences
|
|
101
|
+
|
|
102
|
+
Raises:
|
|
103
|
+
ClickException: If API request fails
|
|
104
|
+
"""
|
|
74
105
|
try:
|
|
75
106
|
client = get_client(ctx)
|
|
76
107
|
with spinner_context(
|
|
@@ -111,36 +142,83 @@ def list_mcps(ctx: Any) -> None:
|
|
|
111
142
|
@click.option("--name", required=True, help="MCP name")
|
|
112
143
|
@click.option("--transport", required=True, help="MCP transport protocol")
|
|
113
144
|
@click.option("--description", help="MCP description")
|
|
114
|
-
@click.option(
|
|
145
|
+
@click.option(
|
|
146
|
+
"--config",
|
|
147
|
+
help="JSON configuration string or @file reference (e.g., @config.json)",
|
|
148
|
+
)
|
|
149
|
+
@click.option(
|
|
150
|
+
"--auth",
|
|
151
|
+
"--authentication",
|
|
152
|
+
"auth",
|
|
153
|
+
help="JSON authentication object or @file reference (e.g., @auth.json)",
|
|
154
|
+
)
|
|
115
155
|
@output_flags()
|
|
116
156
|
@click.pass_context
|
|
117
157
|
def create(
|
|
118
|
-
ctx: Any,
|
|
158
|
+
ctx: Any,
|
|
159
|
+
name: str,
|
|
160
|
+
transport: str,
|
|
161
|
+
description: str | None,
|
|
162
|
+
config: str | None,
|
|
163
|
+
auth: str | None,
|
|
119
164
|
) -> None:
|
|
120
|
-
"""Create a new MCP.
|
|
165
|
+
"""Create a new MCP with specified configuration.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
ctx: Click context containing output format preferences
|
|
169
|
+
name: MCP name (required)
|
|
170
|
+
transport: MCP transport protocol (required)
|
|
171
|
+
description: Optional MCP description
|
|
172
|
+
config: JSON configuration string or @file reference
|
|
173
|
+
auth: JSON authentication object or @file reference
|
|
174
|
+
|
|
175
|
+
Raises:
|
|
176
|
+
ClickException: If JSON parsing fails or API request fails
|
|
177
|
+
"""
|
|
121
178
|
try:
|
|
122
179
|
client = get_client(ctx)
|
|
123
180
|
|
|
124
|
-
# Parse config if provided
|
|
125
|
-
|
|
126
|
-
if
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
181
|
+
# Parse config if provided (supports inline JSON or @file)
|
|
182
|
+
raw_config = parse_json_input(config)
|
|
183
|
+
if raw_config is None:
|
|
184
|
+
mcp_config: dict[str, Any] = {}
|
|
185
|
+
else:
|
|
186
|
+
mcp_config = validate_mcp_config_structure(
|
|
187
|
+
raw_config,
|
|
188
|
+
transport=transport,
|
|
189
|
+
source="--config",
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
# Parse authentication if provided (supports inline JSON or @file)
|
|
193
|
+
mcp_auth = parse_json_input(auth)
|
|
194
|
+
validated_auth = (
|
|
195
|
+
validate_mcp_auth_structure(mcp_auth, source="--auth")
|
|
196
|
+
if mcp_auth is not None
|
|
197
|
+
else None
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
# Build kwargs for create_mcp
|
|
201
|
+
create_kwargs = {
|
|
202
|
+
"name": name,
|
|
203
|
+
"type": "server", # MCPs are always server type
|
|
204
|
+
"transport": transport,
|
|
205
|
+
"config": mcp_config,
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
# Only add description if provided
|
|
209
|
+
if description is not None:
|
|
210
|
+
create_kwargs["description"] = description
|
|
211
|
+
|
|
212
|
+
# Only add authentication if provided
|
|
213
|
+
if validated_auth:
|
|
214
|
+
create_kwargs["authentication"] = validated_auth
|
|
131
215
|
|
|
132
216
|
with spinner_context(
|
|
133
217
|
ctx,
|
|
134
218
|
"[bold blue]Creating MCP…[/bold blue]",
|
|
135
219
|
console_override=console,
|
|
136
220
|
):
|
|
137
|
-
mcp = client.mcps.create_mcp(
|
|
138
|
-
name=name,
|
|
139
|
-
type="server", # MCPs are always server type
|
|
140
|
-
transport=transport,
|
|
141
|
-
description=description,
|
|
142
|
-
config=mcp_config,
|
|
143
|
-
)
|
|
221
|
+
mcp = client.mcps.create_mcp(**create_kwargs)
|
|
144
222
|
|
|
145
223
|
# Handle JSON output
|
|
146
224
|
handle_json_output(ctx, mcp.model_dump())
|
|
@@ -163,22 +241,191 @@ def create(
|
|
|
163
241
|
raise click.ClickException(str(e))
|
|
164
242
|
|
|
165
243
|
|
|
244
|
+
def _handle_mcp_export(
|
|
245
|
+
ctx: Any,
|
|
246
|
+
client: Any,
|
|
247
|
+
mcp: Any,
|
|
248
|
+
export_path: Path,
|
|
249
|
+
no_auth_prompt: bool,
|
|
250
|
+
auth_placeholder: str,
|
|
251
|
+
) -> None:
|
|
252
|
+
"""Handle MCP export to file with format detection and auth handling.
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
ctx: Click context for spinner management
|
|
256
|
+
client: API client for fetching MCP details
|
|
257
|
+
mcp: MCP object to export
|
|
258
|
+
export_path: Target file path (format detected from extension)
|
|
259
|
+
no_auth_prompt: Skip interactive secret prompts if True
|
|
260
|
+
auth_placeholder: Placeholder text for missing secrets
|
|
261
|
+
|
|
262
|
+
Note:
|
|
263
|
+
Supports JSON (.json) and YAML (.yaml/.yml) export formats.
|
|
264
|
+
In interactive mode, prompts for secret values.
|
|
265
|
+
In non-interactive mode, uses placeholder values.
|
|
266
|
+
"""
|
|
267
|
+
# Auto-detect format from file extension
|
|
268
|
+
detected_format = detect_export_format(export_path)
|
|
269
|
+
|
|
270
|
+
# Always export comprehensive data - re-fetch with full details
|
|
271
|
+
try:
|
|
272
|
+
with spinner_context(
|
|
273
|
+
ctx,
|
|
274
|
+
"[bold blue]Fetching complete MCP details…[/bold blue]",
|
|
275
|
+
console_override=console,
|
|
276
|
+
):
|
|
277
|
+
mcp = client.mcps.get_mcp_by_id(mcp.id)
|
|
278
|
+
except Exception as e:
|
|
279
|
+
console.print(
|
|
280
|
+
Text(f"[yellow]⚠️ Could not fetch full MCP details: {e}[/yellow]")
|
|
281
|
+
)
|
|
282
|
+
console.print(Text("[yellow]⚠️ Proceeding with available data[/yellow]"))
|
|
283
|
+
|
|
284
|
+
# Determine if we should prompt for secrets
|
|
285
|
+
prompt_for_secrets = not no_auth_prompt and sys.stdin.isatty()
|
|
286
|
+
|
|
287
|
+
# Warn user if non-interactive mode forces placeholder usage
|
|
288
|
+
if not no_auth_prompt and not sys.stdin.isatty():
|
|
289
|
+
console.print(
|
|
290
|
+
Text(
|
|
291
|
+
"[yellow]⚠️ Non-interactive mode detected. "
|
|
292
|
+
"Using placeholder values for secrets.[/yellow]"
|
|
293
|
+
)
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
# Build and write export payload
|
|
297
|
+
if prompt_for_secrets:
|
|
298
|
+
# Interactive mode: no spinner during prompts
|
|
299
|
+
export_payload = build_mcp_export_payload(
|
|
300
|
+
mcp,
|
|
301
|
+
prompt_for_secrets=prompt_for_secrets,
|
|
302
|
+
placeholder=auth_placeholder,
|
|
303
|
+
console=console,
|
|
304
|
+
)
|
|
305
|
+
with spinner_context(
|
|
306
|
+
ctx,
|
|
307
|
+
"[bold blue]Writing export file…[/bold blue]",
|
|
308
|
+
console_override=console,
|
|
309
|
+
):
|
|
310
|
+
write_resource_export(export_path, export_payload, detected_format)
|
|
311
|
+
else:
|
|
312
|
+
# Non-interactive mode: spinner for entire export process
|
|
313
|
+
with spinner_context(
|
|
314
|
+
ctx,
|
|
315
|
+
"[bold blue]Exporting MCP configuration…[/bold blue]",
|
|
316
|
+
console_override=console,
|
|
317
|
+
):
|
|
318
|
+
export_payload = build_mcp_export_payload(
|
|
319
|
+
mcp,
|
|
320
|
+
prompt_for_secrets=prompt_for_secrets,
|
|
321
|
+
placeholder=auth_placeholder,
|
|
322
|
+
console=console,
|
|
323
|
+
)
|
|
324
|
+
write_resource_export(export_path, export_payload, detected_format)
|
|
325
|
+
|
|
326
|
+
console.print(
|
|
327
|
+
Text(
|
|
328
|
+
f"[green]✅ Complete MCP configuration exported to: "
|
|
329
|
+
f"{export_path} (format: {detected_format})[/green]"
|
|
330
|
+
)
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def _display_mcp_details(ctx: Any, client: Any, mcp: Any) -> None:
|
|
335
|
+
"""Display MCP details using raw API data or fallback to Pydantic model.
|
|
336
|
+
|
|
337
|
+
Args:
|
|
338
|
+
ctx: Click context containing output format preferences
|
|
339
|
+
client: API client for fetching raw MCP data
|
|
340
|
+
mcp: MCP object to display details for
|
|
341
|
+
|
|
342
|
+
Note:
|
|
343
|
+
Attempts to fetch raw API data first to preserve all fields.
|
|
344
|
+
Falls back to Pydantic model data if raw data unavailable.
|
|
345
|
+
Formats datetime fields for better readability.
|
|
346
|
+
"""
|
|
347
|
+
# Try to fetch raw API data first to preserve ALL fields
|
|
348
|
+
with spinner_context(
|
|
349
|
+
ctx,
|
|
350
|
+
"[bold blue]Fetching detailed MCP data…[/bold blue]",
|
|
351
|
+
console_override=console,
|
|
352
|
+
):
|
|
353
|
+
raw_mcp_data = fetch_raw_resource_details(client, mcp, "mcps")
|
|
354
|
+
|
|
355
|
+
if raw_mcp_data:
|
|
356
|
+
# Use raw API data - this preserves ALL fields
|
|
357
|
+
formatted_data = raw_mcp_data.copy()
|
|
358
|
+
if "created_at" in formatted_data:
|
|
359
|
+
formatted_data["created_at"] = format_datetime(formatted_data["created_at"])
|
|
360
|
+
if "updated_at" in formatted_data:
|
|
361
|
+
formatted_data["updated_at"] = format_datetime(formatted_data["updated_at"])
|
|
362
|
+
|
|
363
|
+
output_result(
|
|
364
|
+
ctx,
|
|
365
|
+
formatted_data,
|
|
366
|
+
title="MCP Details",
|
|
367
|
+
panel_title=f"🔌 {raw_mcp_data.get('name', 'Unknown')}",
|
|
368
|
+
)
|
|
369
|
+
else:
|
|
370
|
+
# Fall back to Pydantic model data
|
|
371
|
+
console.print("[yellow]Falling back to Pydantic model data[/yellow]")
|
|
372
|
+
result_data = {
|
|
373
|
+
"id": str(getattr(mcp, "id", "N/A")),
|
|
374
|
+
"name": getattr(mcp, "name", "N/A"),
|
|
375
|
+
"type": getattr(mcp, "type", "N/A"),
|
|
376
|
+
"config": getattr(mcp, "config", "N/A"),
|
|
377
|
+
"status": getattr(mcp, "status", "N/A"),
|
|
378
|
+
"connection_status": getattr(mcp, "connection_status", "N/A"),
|
|
379
|
+
}
|
|
380
|
+
output_result(
|
|
381
|
+
ctx, result_data, title="MCP Details", panel_title=f"🔌 {mcp.name}"
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
|
|
166
385
|
@mcps_group.command()
|
|
167
386
|
@click.argument("mcp_ref")
|
|
168
387
|
@click.option(
|
|
169
388
|
"--export",
|
|
170
389
|
type=click.Path(dir_okay=False, writable=True),
|
|
171
|
-
help="Export complete MCP configuration to file
|
|
390
|
+
help="Export complete MCP configuration to file "
|
|
391
|
+
"(format auto-detected from .json/.yaml extension)",
|
|
392
|
+
)
|
|
393
|
+
@click.option(
|
|
394
|
+
"--no-auth-prompt",
|
|
395
|
+
is_flag=True,
|
|
396
|
+
help="Skip interactive secret prompts and use placeholder values.",
|
|
397
|
+
)
|
|
398
|
+
@click.option(
|
|
399
|
+
"--auth-placeholder",
|
|
400
|
+
default="<INSERT VALUE>",
|
|
401
|
+
show_default=True,
|
|
402
|
+
help="Placeholder text used when secrets are unavailable.",
|
|
172
403
|
)
|
|
173
404
|
@output_flags()
|
|
174
405
|
@click.pass_context
|
|
175
|
-
def get(
|
|
176
|
-
|
|
406
|
+
def get(
|
|
407
|
+
ctx: Any,
|
|
408
|
+
mcp_ref: str,
|
|
409
|
+
export: str | None,
|
|
410
|
+
no_auth_prompt: bool,
|
|
411
|
+
auth_placeholder: str,
|
|
412
|
+
) -> None:
|
|
413
|
+
"""Get MCP details and optionally export configuration to file.
|
|
414
|
+
|
|
415
|
+
Args:
|
|
416
|
+
ctx: Click context containing output format preferences
|
|
417
|
+
mcp_ref: MCP reference (ID or name)
|
|
418
|
+
export: Optional file path to export MCP configuration
|
|
419
|
+
no_auth_prompt: Skip interactive secret prompts if True
|
|
420
|
+
auth_placeholder: Placeholder text for missing secrets
|
|
421
|
+
|
|
422
|
+
Raises:
|
|
423
|
+
ClickException: If MCP not found or export fails
|
|
177
424
|
|
|
178
425
|
Examples:
|
|
179
426
|
aip mcps get my-mcp
|
|
180
|
-
aip mcps get my-mcp --export mcp.json #
|
|
181
|
-
aip mcps get my-mcp --export mcp.yaml #
|
|
427
|
+
aip mcps get my-mcp --export mcp.json # Export as JSON
|
|
428
|
+
aip mcps get my-mcp --export mcp.yaml # Export as YAML
|
|
182
429
|
"""
|
|
183
430
|
try:
|
|
184
431
|
client = get_client(ctx)
|
|
@@ -188,83 +435,12 @@ def get(ctx: Any, mcp_ref: str, export: str | None) -> None:
|
|
|
188
435
|
|
|
189
436
|
# Handle export option
|
|
190
437
|
if export:
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
detected_format = detect_export_format(export_path)
|
|
194
|
-
|
|
195
|
-
# Always export comprehensive data - re-fetch MCP with full details if needed
|
|
196
|
-
try:
|
|
197
|
-
with spinner_context(
|
|
198
|
-
ctx,
|
|
199
|
-
"[bold blue]Fetching complete MCP details…[/bold blue]",
|
|
200
|
-
console_override=console,
|
|
201
|
-
):
|
|
202
|
-
mcp = client.mcps.get_mcp_by_id(mcp.id)
|
|
203
|
-
except Exception as e:
|
|
204
|
-
console.print(
|
|
205
|
-
Text(f"[yellow]⚠️ Could not fetch full MCP details: {e}[/yellow]")
|
|
206
|
-
)
|
|
207
|
-
console.print(
|
|
208
|
-
Text("[yellow]⚠️ Proceeding with available data[/yellow]")
|
|
209
|
-
)
|
|
210
|
-
|
|
211
|
-
with spinner_context(
|
|
212
|
-
ctx,
|
|
213
|
-
"[bold blue]Exporting MCP configuration…[/bold blue]",
|
|
214
|
-
console_override=console,
|
|
215
|
-
):
|
|
216
|
-
export_resource_to_file(mcp, export_path, detected_format)
|
|
217
|
-
console.print(
|
|
218
|
-
Text(
|
|
219
|
-
f"[green]✅ Complete MCP configuration exported to: {export_path} (format: {detected_format})[/green]"
|
|
220
|
-
)
|
|
221
|
-
)
|
|
222
|
-
|
|
223
|
-
# Try to fetch raw API data first to preserve ALL fields
|
|
224
|
-
with spinner_context(
|
|
225
|
-
ctx,
|
|
226
|
-
"[bold blue]Fetching detailed MCP data…[/bold blue]",
|
|
227
|
-
console_override=console,
|
|
228
|
-
):
|
|
229
|
-
raw_mcp_data = fetch_raw_resource_details(client, mcp, "mcps")
|
|
230
|
-
|
|
231
|
-
if raw_mcp_data:
|
|
232
|
-
# Use raw API data - this preserves ALL fields
|
|
233
|
-
# Format dates for better display (minimal postprocessing)
|
|
234
|
-
formatted_data = raw_mcp_data.copy()
|
|
235
|
-
if "created_at" in formatted_data:
|
|
236
|
-
formatted_data["created_at"] = format_datetime(
|
|
237
|
-
formatted_data["created_at"]
|
|
238
|
-
)
|
|
239
|
-
if "updated_at" in formatted_data:
|
|
240
|
-
formatted_data["updated_at"] = format_datetime(
|
|
241
|
-
formatted_data["updated_at"]
|
|
242
|
-
)
|
|
243
|
-
|
|
244
|
-
# Display using output_result with raw data
|
|
245
|
-
output_result(
|
|
246
|
-
ctx,
|
|
247
|
-
formatted_data,
|
|
248
|
-
title="MCP Details",
|
|
249
|
-
panel_title=f"🔌 {raw_mcp_data.get('name', 'Unknown')}",
|
|
438
|
+
_handle_mcp_export(
|
|
439
|
+
ctx, client, mcp, Path(export), no_auth_prompt, auth_placeholder
|
|
250
440
|
)
|
|
251
|
-
else:
|
|
252
|
-
# Fall back to original method if raw fetch fails
|
|
253
|
-
console.print("[yellow]Falling back to Pydantic model data[/yellow]")
|
|
254
|
-
|
|
255
|
-
# Create result data with actual available fields
|
|
256
|
-
result_data = {
|
|
257
|
-
"id": str(getattr(mcp, "id", "N/A")),
|
|
258
|
-
"name": getattr(mcp, "name", "N/A"),
|
|
259
|
-
"type": getattr(mcp, "type", "N/A"),
|
|
260
|
-
"config": getattr(mcp, "config", "N/A"),
|
|
261
|
-
"status": getattr(mcp, "status", "N/A"),
|
|
262
|
-
"connection_status": getattr(mcp, "connection_status", "N/A"),
|
|
263
|
-
}
|
|
264
441
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
)
|
|
442
|
+
# Display MCP details
|
|
443
|
+
_display_mcp_details(ctx, client, mcp)
|
|
268
444
|
|
|
269
445
|
except Exception as e:
|
|
270
446
|
raise click.ClickException(str(e))
|
|
@@ -275,7 +451,15 @@ def get(ctx: Any, mcp_ref: str, export: str | None) -> None:
|
|
|
275
451
|
@output_flags()
|
|
276
452
|
@click.pass_context
|
|
277
453
|
def list_tools(ctx: Any, mcp_ref: str) -> None:
|
|
278
|
-
"""List tools from MCP.
|
|
454
|
+
"""List tools available from a specific MCP.
|
|
455
|
+
|
|
456
|
+
Args:
|
|
457
|
+
ctx: Click context containing output format preferences
|
|
458
|
+
mcp_ref: MCP reference (ID or name)
|
|
459
|
+
|
|
460
|
+
Raises:
|
|
461
|
+
ClickException: If MCP not found or tools fetch fails
|
|
462
|
+
"""
|
|
279
463
|
try:
|
|
280
464
|
client = get_client(ctx)
|
|
281
465
|
|
|
@@ -325,7 +509,19 @@ def list_tools(ctx: Any, mcp_ref: str) -> None:
|
|
|
325
509
|
@output_flags()
|
|
326
510
|
@click.pass_context
|
|
327
511
|
def connect(ctx: Any, config_file: str) -> None:
|
|
328
|
-
"""
|
|
512
|
+
"""Test MCP connection using a configuration file.
|
|
513
|
+
|
|
514
|
+
Args:
|
|
515
|
+
ctx: Click context containing output format preferences
|
|
516
|
+
config_file: Path to MCP configuration JSON file
|
|
517
|
+
|
|
518
|
+
Raises:
|
|
519
|
+
ClickException: If config file invalid or connection test fails
|
|
520
|
+
|
|
521
|
+
Note:
|
|
522
|
+
Loads MCP configuration from JSON file and tests connectivity.
|
|
523
|
+
Displays success or failure with connection details.
|
|
524
|
+
"""
|
|
329
525
|
try:
|
|
330
526
|
client = get_client(ctx)
|
|
331
527
|
|
|
@@ -337,7 +533,8 @@ def connect(ctx: Any, config_file: str) -> None:
|
|
|
337
533
|
if view != "json":
|
|
338
534
|
console.print(
|
|
339
535
|
Text(
|
|
340
|
-
f"[yellow]Connecting to MCP with config from
|
|
536
|
+
f"[yellow]Connecting to MCP with config from "
|
|
537
|
+
f"{config_file}...[/yellow]"
|
|
341
538
|
)
|
|
342
539
|
)
|
|
343
540
|
|
|
@@ -369,7 +566,16 @@ def connect(ctx: Any, config_file: str) -> None:
|
|
|
369
566
|
@click.argument("mcp_ref")
|
|
370
567
|
@click.option("--name", help="New MCP name")
|
|
371
568
|
@click.option("--description", help="New description")
|
|
372
|
-
@click.option(
|
|
569
|
+
@click.option(
|
|
570
|
+
"--config",
|
|
571
|
+
help="JSON configuration string or @file reference (e.g., @config.json)",
|
|
572
|
+
)
|
|
573
|
+
@click.option(
|
|
574
|
+
"--auth",
|
|
575
|
+
"--authentication",
|
|
576
|
+
"auth",
|
|
577
|
+
help="JSON authentication object or @file reference (e.g., @auth.json)",
|
|
578
|
+
)
|
|
373
579
|
@output_flags()
|
|
374
580
|
@click.pass_context
|
|
375
581
|
def update(
|
|
@@ -378,8 +584,25 @@ def update(
|
|
|
378
584
|
name: str | None,
|
|
379
585
|
description: str | None,
|
|
380
586
|
config: str | None,
|
|
587
|
+
auth: str | None,
|
|
381
588
|
) -> None:
|
|
382
|
-
"""Update an existing MCP.
|
|
589
|
+
"""Update an existing MCP with new configuration values.
|
|
590
|
+
|
|
591
|
+
Args:
|
|
592
|
+
ctx: Click context containing output format preferences
|
|
593
|
+
mcp_ref: MCP reference (ID or name)
|
|
594
|
+
name: New MCP name (optional)
|
|
595
|
+
description: New description (optional)
|
|
596
|
+
config: New JSON configuration string or @file reference (optional)
|
|
597
|
+
auth: New JSON authentication object or @file reference (optional)
|
|
598
|
+
|
|
599
|
+
Raises:
|
|
600
|
+
ClickException: If MCP not found, JSON invalid, or no fields specified
|
|
601
|
+
|
|
602
|
+
Note:
|
|
603
|
+
At least one field must be specified for update.
|
|
604
|
+
Uses PUT for complete updates or PATCH for partial updates.
|
|
605
|
+
"""
|
|
383
606
|
try:
|
|
384
607
|
client = get_client(ctx)
|
|
385
608
|
|
|
@@ -393,10 +616,17 @@ def update(
|
|
|
393
616
|
if description is not None:
|
|
394
617
|
update_data["description"] = description
|
|
395
618
|
if config is not None:
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
619
|
+
parsed_config = parse_json_input(config)
|
|
620
|
+
update_data["config"] = validate_mcp_config_structure(
|
|
621
|
+
parsed_config,
|
|
622
|
+
transport=getattr(mcp, "transport", None),
|
|
623
|
+
source="--config",
|
|
624
|
+
)
|
|
625
|
+
if auth is not None:
|
|
626
|
+
parsed_auth = parse_json_input(auth)
|
|
627
|
+
update_data["authentication"] = validate_mcp_auth_structure(
|
|
628
|
+
parsed_auth, source="--auth"
|
|
629
|
+
)
|
|
400
630
|
|
|
401
631
|
if not update_data:
|
|
402
632
|
raise click.ClickException("No update fields specified")
|
|
@@ -425,7 +655,20 @@ def update(
|
|
|
425
655
|
@output_flags()
|
|
426
656
|
@click.pass_context
|
|
427
657
|
def delete(ctx: Any, mcp_ref: str, yes: bool) -> None:
|
|
428
|
-
"""Delete an MCP.
|
|
658
|
+
"""Delete an MCP after confirmation.
|
|
659
|
+
|
|
660
|
+
Args:
|
|
661
|
+
ctx: Click context containing output format preferences
|
|
662
|
+
mcp_ref: MCP reference (ID or name)
|
|
663
|
+
yes: Skip confirmation prompt if True
|
|
664
|
+
|
|
665
|
+
Raises:
|
|
666
|
+
ClickException: If MCP not found or deletion fails
|
|
667
|
+
|
|
668
|
+
Note:
|
|
669
|
+
Requires confirmation unless --yes flag is provided.
|
|
670
|
+
Deletion is permanent and cannot be undone.
|
|
671
|
+
"""
|
|
429
672
|
try:
|
|
430
673
|
client = get_client(ctx)
|
|
431
674
|
|
glaip_sdk/cli/main.py
CHANGED
|
@@ -24,6 +24,7 @@ from glaip_sdk.cli.commands.configure import (
|
|
|
24
24
|
from glaip_sdk.cli.commands.mcps import mcps_group
|
|
25
25
|
from glaip_sdk.cli.commands.models import models_group
|
|
26
26
|
from glaip_sdk.cli.commands.tools import tools_group
|
|
27
|
+
from glaip_sdk.cli.update_notifier import maybe_notify_update
|
|
27
28
|
from glaip_sdk.cli.utils import spinner_context, update_spinner
|
|
28
29
|
from glaip_sdk.config.constants import (
|
|
29
30
|
DEFAULT_AGENT_RUN_TIMEOUT,
|
|
@@ -82,6 +83,13 @@ def main(
|
|
|
82
83
|
|
|
83
84
|
ctx.obj["tty"] = not no_tty
|
|
84
85
|
|
|
86
|
+
if not ctx.resilient_parsing and ctx.obj["tty"]:
|
|
87
|
+
console = Console()
|
|
88
|
+
maybe_notify_update(
|
|
89
|
+
_SDK_VERSION,
|
|
90
|
+
console=console,
|
|
91
|
+
)
|
|
92
|
+
|
|
85
93
|
if ctx.invoked_subcommand is None and not ctx.resilient_parsing:
|
|
86
94
|
if _should_launch_slash(ctx) and SlashSession is not None:
|
|
87
95
|
session = SlashSession(ctx)
|