glaip-sdk 0.0.1b5__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 +12 -0
- glaip_sdk/cli/__init__.py +9 -0
- glaip_sdk/cli/commands/__init__.py +5 -0
- glaip_sdk/cli/commands/agents.py +415 -0
- glaip_sdk/cli/commands/configure.py +316 -0
- glaip_sdk/cli/commands/init.py +168 -0
- glaip_sdk/cli/commands/mcps.py +473 -0
- glaip_sdk/cli/commands/models.py +52 -0
- glaip_sdk/cli/commands/tools.py +309 -0
- glaip_sdk/cli/config.py +592 -0
- glaip_sdk/cli/main.py +298 -0
- glaip_sdk/cli/utils.py +733 -0
- glaip_sdk/client/__init__.py +179 -0
- glaip_sdk/client/agents.py +441 -0
- glaip_sdk/client/base.py +223 -0
- glaip_sdk/client/mcps.py +94 -0
- glaip_sdk/client/tools.py +193 -0
- glaip_sdk/client/validators.py +166 -0
- glaip_sdk/config/constants.py +28 -0
- glaip_sdk/exceptions.py +93 -0
- glaip_sdk/models.py +190 -0
- glaip_sdk/utils/__init__.py +95 -0
- glaip_sdk/utils/run_renderer.py +1009 -0
- glaip_sdk/utils.py +167 -0
- glaip_sdk-0.0.1b5.dist-info/METADATA +633 -0
- glaip_sdk-0.0.1b5.dist-info/RECORD +28 -0
- glaip_sdk-0.0.1b5.dist-info/WHEEL +4 -0
- glaip_sdk-0.0.1b5.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
"""MCP management commands.
|
|
2
|
+
|
|
3
|
+
Authors:
|
|
4
|
+
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
|
|
9
|
+
import click
|
|
10
|
+
from rich.console import Console
|
|
11
|
+
from rich.panel import Panel
|
|
12
|
+
from rich.table import Table
|
|
13
|
+
|
|
14
|
+
from glaip_sdk.utils import is_uuid
|
|
15
|
+
|
|
16
|
+
from ..utils import (
|
|
17
|
+
get_client,
|
|
18
|
+
handle_ambiguous_resource,
|
|
19
|
+
output_flags,
|
|
20
|
+
output_list,
|
|
21
|
+
output_result,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
console = Console()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@click.group(name="mcps", no_args_is_help=True)
|
|
28
|
+
def mcps_group():
|
|
29
|
+
"""MCP management operations."""
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _resolve_mcp(ctx, client, ref, select=None):
|
|
34
|
+
"""Resolve MCP reference (ID or name) with ambiguity handling."""
|
|
35
|
+
if is_uuid(ref):
|
|
36
|
+
return client.mcps.get_mcp_by_id(ref)
|
|
37
|
+
|
|
38
|
+
# Find MCPs by name
|
|
39
|
+
matches = client.mcps.find_mcps(name=ref)
|
|
40
|
+
if not matches:
|
|
41
|
+
raise click.ClickException(f"MCP '{ref}' not found")
|
|
42
|
+
|
|
43
|
+
if len(matches) == 1:
|
|
44
|
+
return matches[0]
|
|
45
|
+
|
|
46
|
+
# Multiple matches - handle ambiguity
|
|
47
|
+
if select:
|
|
48
|
+
idx = int(select) - 1
|
|
49
|
+
if not (0 <= idx < len(matches)):
|
|
50
|
+
raise click.ClickException(f"--select must be 1..{len(matches)}")
|
|
51
|
+
return matches[idx]
|
|
52
|
+
|
|
53
|
+
return handle_ambiguous_resource(ctx, "MCP", ref, matches)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@mcps_group.command(name="list")
|
|
57
|
+
@output_flags()
|
|
58
|
+
@click.pass_context
|
|
59
|
+
def list_mcps(ctx):
|
|
60
|
+
"""List all MCPs."""
|
|
61
|
+
try:
|
|
62
|
+
client = get_client(ctx)
|
|
63
|
+
mcps = client.mcps.list_mcps()
|
|
64
|
+
|
|
65
|
+
# Define table columns: (data_key, header, style, width)
|
|
66
|
+
columns = [
|
|
67
|
+
("id", "ID", "dim", 36),
|
|
68
|
+
("name", "Name", "cyan", None),
|
|
69
|
+
("config", "Config", "blue", None),
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
# Transform function for safe dictionary access
|
|
73
|
+
def transform_mcp(mcp):
|
|
74
|
+
# Handle both dict and object formats
|
|
75
|
+
if isinstance(mcp, dict):
|
|
76
|
+
return {
|
|
77
|
+
"id": str(mcp.get("id", "N/A")),
|
|
78
|
+
"name": mcp.get("name", "N/A"),
|
|
79
|
+
"config": str(mcp.get("config", "N/A"))[:50] + "..."
|
|
80
|
+
if mcp.get("config")
|
|
81
|
+
else "N/A",
|
|
82
|
+
}
|
|
83
|
+
else:
|
|
84
|
+
# Fallback to attribute access
|
|
85
|
+
return {
|
|
86
|
+
"id": str(getattr(mcp, "id", "N/A")),
|
|
87
|
+
"name": getattr(mcp, "name", "N/A"),
|
|
88
|
+
"config": str(getattr(mcp, "config", "N/A"))[:50] + "..."
|
|
89
|
+
if getattr(mcp, "config", None)
|
|
90
|
+
else "N/A",
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
output_list(ctx, mcps, "🔌 Available MCPs", columns, transform_mcp)
|
|
94
|
+
|
|
95
|
+
except Exception as e:
|
|
96
|
+
raise click.ClickException(str(e))
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@mcps_group.command()
|
|
100
|
+
@click.option("--name", required=True, help="MCP name")
|
|
101
|
+
@click.option("--transport", required=True, help="MCP transport protocol")
|
|
102
|
+
@click.option("--description", help="MCP description")
|
|
103
|
+
@click.option("--config", help="JSON configuration string")
|
|
104
|
+
@output_flags()
|
|
105
|
+
@click.pass_context
|
|
106
|
+
def create(ctx, name, transport, description, config):
|
|
107
|
+
"""Create a new MCP."""
|
|
108
|
+
try:
|
|
109
|
+
client = get_client(ctx)
|
|
110
|
+
|
|
111
|
+
# Parse config if provided
|
|
112
|
+
mcp_config = {}
|
|
113
|
+
if config:
|
|
114
|
+
try:
|
|
115
|
+
mcp_config = json.loads(config)
|
|
116
|
+
except json.JSONDecodeError:
|
|
117
|
+
raise click.ClickException("Invalid JSON in --config")
|
|
118
|
+
|
|
119
|
+
mcp = client.mcps.create_mcp(
|
|
120
|
+
name=name,
|
|
121
|
+
type="server", # Always server type
|
|
122
|
+
transport=transport,
|
|
123
|
+
description=description,
|
|
124
|
+
config=mcp_config,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
if ctx.obj.get("view") == "json":
|
|
128
|
+
click.echo(json.dumps(mcp.model_dump(), indent=2))
|
|
129
|
+
else:
|
|
130
|
+
# Rich output
|
|
131
|
+
panel = Panel(
|
|
132
|
+
f"[green]✅ MCP '{mcp.name}' created successfully![/green]\n\n"
|
|
133
|
+
f"ID: {mcp.id}\n"
|
|
134
|
+
f"Type: server (default)\n"
|
|
135
|
+
f"Description: {description or 'No description'}",
|
|
136
|
+
title="🔌 MCP Created",
|
|
137
|
+
border_style="green",
|
|
138
|
+
)
|
|
139
|
+
console.print(panel)
|
|
140
|
+
|
|
141
|
+
except Exception as e:
|
|
142
|
+
if ctx.obj.get("view") == "json":
|
|
143
|
+
click.echo(json.dumps({"error": str(e)}, indent=2))
|
|
144
|
+
else:
|
|
145
|
+
console.print(f"[red]Error creating MCP: {e}[/red]")
|
|
146
|
+
raise click.ClickException(str(e))
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
@mcps_group.command()
|
|
150
|
+
@click.argument("mcp_ref")
|
|
151
|
+
@output_flags()
|
|
152
|
+
@click.pass_context
|
|
153
|
+
def get(ctx, mcp_ref):
|
|
154
|
+
"""Get MCP details."""
|
|
155
|
+
try:
|
|
156
|
+
client = get_client(ctx)
|
|
157
|
+
|
|
158
|
+
# Get MCP by ID or name (shows all matches for names)
|
|
159
|
+
if is_uuid(mcp_ref):
|
|
160
|
+
mcp = client.mcps.get_mcp_by_id(mcp_ref)
|
|
161
|
+
else:
|
|
162
|
+
# Find MCPs by name
|
|
163
|
+
matches = client.mcps.find_mcps(name=mcp_ref)
|
|
164
|
+
if not matches:
|
|
165
|
+
raise click.ClickException(f"MCP '{mcp_ref}' not found")
|
|
166
|
+
elif len(matches) == 1:
|
|
167
|
+
mcp = matches[0]
|
|
168
|
+
else:
|
|
169
|
+
# Show all matches
|
|
170
|
+
console.print(
|
|
171
|
+
f"[yellow]Multiple MCPs found with name '{mcp_ref}':[/yellow]"
|
|
172
|
+
)
|
|
173
|
+
for i, match in enumerate(matches, 1):
|
|
174
|
+
console.print(f" {i}. {match.name} (ID: {match.id})")
|
|
175
|
+
console.print("[yellow]Use the ID for unambiguous operations.[/yellow]")
|
|
176
|
+
return
|
|
177
|
+
|
|
178
|
+
# Create result data with actual available fields
|
|
179
|
+
result_data = {
|
|
180
|
+
"id": str(getattr(mcp, "id", "N/A")),
|
|
181
|
+
"name": getattr(mcp, "name", "N/A"),
|
|
182
|
+
"type": getattr(mcp, "type", "N/A"),
|
|
183
|
+
"config": getattr(mcp, "config", "N/A"),
|
|
184
|
+
"status": getattr(mcp, "status", "N/A"),
|
|
185
|
+
"connection_status": getattr(mcp, "connection_status", "N/A"),
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
output_result(
|
|
189
|
+
ctx, result_data, title="MCP Details", panel_title=f"🔌 {mcp.name}"
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
except Exception as e:
|
|
193
|
+
raise click.ClickException(str(e))
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
@mcps_group.command("tools")
|
|
197
|
+
@click.argument("mcp_ref")
|
|
198
|
+
@output_flags()
|
|
199
|
+
@click.pass_context
|
|
200
|
+
def list_tools(ctx, mcp_ref):
|
|
201
|
+
"""List tools from MCP."""
|
|
202
|
+
try:
|
|
203
|
+
client = get_client(ctx)
|
|
204
|
+
|
|
205
|
+
# Get MCP by ID or name (shows all matches for names)
|
|
206
|
+
if is_uuid(mcp_ref):
|
|
207
|
+
mcp = client.get_mcp_by_id(mcp_ref)
|
|
208
|
+
else:
|
|
209
|
+
# Find MCPs by name
|
|
210
|
+
matches = client.find_mcps(name=mcp_ref)
|
|
211
|
+
if not matches:
|
|
212
|
+
raise click.ClickException(f"MCP '{mcp_ref}' not found")
|
|
213
|
+
elif len(matches) == 1:
|
|
214
|
+
mcp = matches[0]
|
|
215
|
+
else:
|
|
216
|
+
# Show all matches
|
|
217
|
+
console.print(
|
|
218
|
+
f"[yellow]Multiple MCPs found with name '{mcp_ref}':[/yellow]"
|
|
219
|
+
)
|
|
220
|
+
for i, match in enumerate(matches, 1):
|
|
221
|
+
console.print(f" {i}. {match.name} (ID: {match.id})")
|
|
222
|
+
console.print("[yellow]Use the ID for unambiguous operations.[/yellow]")
|
|
223
|
+
return
|
|
224
|
+
|
|
225
|
+
# Get tools from MCP
|
|
226
|
+
tools = client.mcps.get_mcp_tools(mcp.id)
|
|
227
|
+
|
|
228
|
+
if ctx.obj.get("view") == "json":
|
|
229
|
+
click.echo(json.dumps(tools, indent=2))
|
|
230
|
+
else: # rich output
|
|
231
|
+
if tools:
|
|
232
|
+
table = Table(
|
|
233
|
+
title=f"🔧 Tools from MCP: {mcp.name}",
|
|
234
|
+
show_header=True,
|
|
235
|
+
header_style="bold magenta",
|
|
236
|
+
)
|
|
237
|
+
table.add_column("Name", style="cyan", no_wrap=True)
|
|
238
|
+
table.add_column("Description", style="green")
|
|
239
|
+
table.add_column("Type", style="yellow")
|
|
240
|
+
|
|
241
|
+
for tool in tools:
|
|
242
|
+
table.add_row(
|
|
243
|
+
tool.get("name", "N/A"),
|
|
244
|
+
tool.get("description", "N/A")[:50] + "..."
|
|
245
|
+
if len(tool.get("description", "")) > 50
|
|
246
|
+
else tool.get("description", "N/A"),
|
|
247
|
+
tool.get("type", "N/A"),
|
|
248
|
+
)
|
|
249
|
+
console.print(table)
|
|
250
|
+
else:
|
|
251
|
+
console.print(f"[yellow]No tools found in MCP '{mcp.name}'[/yellow]")
|
|
252
|
+
|
|
253
|
+
except Exception as e:
|
|
254
|
+
raise click.ClickException(str(e))
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
@mcps_group.command("tools-from-config")
|
|
258
|
+
@click.option(
|
|
259
|
+
"--from-file",
|
|
260
|
+
"config_file",
|
|
261
|
+
type=click.Path(exists=True),
|
|
262
|
+
required=True,
|
|
263
|
+
help="MCP config JSON file",
|
|
264
|
+
)
|
|
265
|
+
@output_flags()
|
|
266
|
+
@click.pass_context
|
|
267
|
+
def tools_from_config(ctx, config_file):
|
|
268
|
+
"""Fetch tools from MCP config."""
|
|
269
|
+
try:
|
|
270
|
+
client = get_client(ctx)
|
|
271
|
+
|
|
272
|
+
# Load MCP config from file
|
|
273
|
+
with open(config_file) as f:
|
|
274
|
+
config = json.load(f)
|
|
275
|
+
|
|
276
|
+
if ctx.obj.get("view") != "json":
|
|
277
|
+
console.print(
|
|
278
|
+
f"[yellow]Fetching tools from MCP config in {config_file}...[/yellow]"
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
# Get tools from MCP config
|
|
282
|
+
tools = client.mcps.get_tools_from_mcp_config(config)
|
|
283
|
+
|
|
284
|
+
if ctx.obj.get("view") == "json":
|
|
285
|
+
click.echo(json.dumps(tools, indent=2))
|
|
286
|
+
else: # rich output
|
|
287
|
+
if tools:
|
|
288
|
+
table = Table(
|
|
289
|
+
title="🔧 Tools from MCP Config",
|
|
290
|
+
show_header=True,
|
|
291
|
+
header_style="bold magenta",
|
|
292
|
+
)
|
|
293
|
+
table.add_column("Name", style="cyan", no_wrap=True)
|
|
294
|
+
table.add_column("Description", style="green")
|
|
295
|
+
table.add_column("Type", style="yellow")
|
|
296
|
+
|
|
297
|
+
for tool in tools:
|
|
298
|
+
table.add_row(
|
|
299
|
+
tool.get("name", "N/A"),
|
|
300
|
+
tool.get("description", "N/A")[:50] + "..."
|
|
301
|
+
if len(tool.get("description", "")) > 50
|
|
302
|
+
else tool.get("description", "N/A"),
|
|
303
|
+
tool.get("type", "N/A"),
|
|
304
|
+
)
|
|
305
|
+
console.print(table)
|
|
306
|
+
else:
|
|
307
|
+
console.print("[yellow]No tools found in MCP config[/yellow]")
|
|
308
|
+
|
|
309
|
+
except Exception as e:
|
|
310
|
+
raise click.ClickException(str(e))
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
@mcps_group.command("test-connection")
|
|
314
|
+
@click.option(
|
|
315
|
+
"--from-file",
|
|
316
|
+
"config_file",
|
|
317
|
+
required=True,
|
|
318
|
+
help="MCP config JSON file",
|
|
319
|
+
)
|
|
320
|
+
@output_flags()
|
|
321
|
+
@click.pass_context
|
|
322
|
+
def test_connection(ctx, config_file):
|
|
323
|
+
"""Test MCP connection using config file."""
|
|
324
|
+
try:
|
|
325
|
+
client = get_client(ctx)
|
|
326
|
+
|
|
327
|
+
# Load MCP config from file
|
|
328
|
+
with open(config_file) as f:
|
|
329
|
+
config = json.load(f)
|
|
330
|
+
|
|
331
|
+
if ctx.obj.get("view") != "json":
|
|
332
|
+
console.print(
|
|
333
|
+
f"[yellow]Testing MCP connection with config from {config_file}...[/yellow]"
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
# Test connection using config
|
|
337
|
+
result = client.mcps.test_mcp_connection_from_config(config)
|
|
338
|
+
|
|
339
|
+
if ctx.obj.get("view") == "json":
|
|
340
|
+
click.echo(json.dumps(result, indent=2))
|
|
341
|
+
else:
|
|
342
|
+
success_panel = Panel(
|
|
343
|
+
f"[green]✓[/green] MCP connection test successful!\n\n"
|
|
344
|
+
f"[bold]Result:[/bold] {result}",
|
|
345
|
+
title="🔌 Connection Test",
|
|
346
|
+
border_style="green",
|
|
347
|
+
)
|
|
348
|
+
console.print(success_panel)
|
|
349
|
+
|
|
350
|
+
except Exception as e:
|
|
351
|
+
raise click.ClickException(str(e))
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
@mcps_group.command()
|
|
355
|
+
@click.argument("mcp_ref")
|
|
356
|
+
@click.option("--name", help="New MCP name")
|
|
357
|
+
@click.option("--description", help="New description")
|
|
358
|
+
@click.option("--config", help="JSON configuration string")
|
|
359
|
+
@output_flags()
|
|
360
|
+
@click.pass_context
|
|
361
|
+
def update(ctx, mcp_ref, name, description, config):
|
|
362
|
+
"""Update an existing MCP."""
|
|
363
|
+
try:
|
|
364
|
+
client = get_client(ctx)
|
|
365
|
+
|
|
366
|
+
# Get MCP by ID or name (shows all matches for names)
|
|
367
|
+
if is_uuid(mcp_ref):
|
|
368
|
+
mcp = client.mcps.get_mcp_by_id(mcp_ref)
|
|
369
|
+
else:
|
|
370
|
+
# Find MCPs by name
|
|
371
|
+
matches = client.mcps.find_mcps(name=mcp_ref)
|
|
372
|
+
if not matches:
|
|
373
|
+
raise click.ClickException(f"MCP '{mcp_ref}' not found")
|
|
374
|
+
elif len(matches) == 1:
|
|
375
|
+
mcp = matches[0]
|
|
376
|
+
else:
|
|
377
|
+
# Show all matches
|
|
378
|
+
console.print(
|
|
379
|
+
f"[yellow]Multiple MCPs found with name '{mcp_ref}':[/yellow]"
|
|
380
|
+
)
|
|
381
|
+
for i, match in enumerate(matches, 1):
|
|
382
|
+
console.print(f" {i}. {match.name} (ID: {match.id})")
|
|
383
|
+
console.print("[yellow]Use the ID for unambiguous operations.[/yellow]")
|
|
384
|
+
return
|
|
385
|
+
|
|
386
|
+
# Build update data
|
|
387
|
+
update_data = {}
|
|
388
|
+
if name is not None:
|
|
389
|
+
update_data["name"] = name
|
|
390
|
+
if description is not None:
|
|
391
|
+
update_data["description"] = description
|
|
392
|
+
if config is not None:
|
|
393
|
+
try:
|
|
394
|
+
update_data["config"] = json.loads(config)
|
|
395
|
+
except json.JSONDecodeError:
|
|
396
|
+
raise click.ClickException("Invalid JSON in --config")
|
|
397
|
+
|
|
398
|
+
if not update_data:
|
|
399
|
+
raise click.ClickException("No update fields specified")
|
|
400
|
+
|
|
401
|
+
# Update MCP
|
|
402
|
+
updated_mcp = client.mcps.update_mcp(mcp.id, update_data)
|
|
403
|
+
|
|
404
|
+
if ctx.obj.get("view") == "json":
|
|
405
|
+
click.echo(json.dumps(updated_mcp.model_dump(), indent=2))
|
|
406
|
+
else:
|
|
407
|
+
console.print(
|
|
408
|
+
f"[green]✅ MCP '{updated_mcp.name}' updated successfully[/green]"
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
except Exception as e:
|
|
412
|
+
if ctx.obj.get("view") == "json":
|
|
413
|
+
click.echo(json.dumps({"error": str(e)}, indent=2))
|
|
414
|
+
else:
|
|
415
|
+
console.print(f"[red]Error updating MCP: {e}[/red]")
|
|
416
|
+
raise click.ClickException(str(e))
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
@mcps_group.command()
|
|
420
|
+
@click.argument("mcp_ref")
|
|
421
|
+
@click.option("-y", "--yes", is_flag=True, help="Skip confirmation")
|
|
422
|
+
@output_flags()
|
|
423
|
+
@click.pass_context
|
|
424
|
+
def delete(ctx, mcp_ref, yes):
|
|
425
|
+
"""Delete an MCP."""
|
|
426
|
+
try:
|
|
427
|
+
client = get_client(ctx)
|
|
428
|
+
|
|
429
|
+
# Get MCP by ID or name (shows all matches for names)
|
|
430
|
+
if is_uuid(mcp_ref):
|
|
431
|
+
mcp = client.mcps.get_mcp_by_id(mcp_ref)
|
|
432
|
+
else:
|
|
433
|
+
# Find MCPs by name
|
|
434
|
+
matches = client.mcps.find_mcps(name=mcp_ref)
|
|
435
|
+
if not matches:
|
|
436
|
+
raise click.ClickException(f"MCP '{mcp_ref}' not found")
|
|
437
|
+
elif len(matches) == 1:
|
|
438
|
+
mcp = matches[0]
|
|
439
|
+
else:
|
|
440
|
+
# Show all matches
|
|
441
|
+
console.print(
|
|
442
|
+
f"[yellow]Multiple MCPs found with name '{mcp_ref}':[/yellow]"
|
|
443
|
+
)
|
|
444
|
+
for i, match in enumerate(matches, 1):
|
|
445
|
+
console.print(f" {i}. {match.name} (ID: {match.id})")
|
|
446
|
+
console.print("[yellow]Use the ID for unambiguous operations.[/yellow]")
|
|
447
|
+
return
|
|
448
|
+
|
|
449
|
+
# Confirm deletion
|
|
450
|
+
if not yes and not click.confirm(
|
|
451
|
+
f"Are you sure you want to delete MCP '{mcp.name}'?"
|
|
452
|
+
):
|
|
453
|
+
if ctx.obj.get("view") != "json":
|
|
454
|
+
console.print("Deletion cancelled.")
|
|
455
|
+
return
|
|
456
|
+
|
|
457
|
+
client.mcps.delete_mcp(mcp.id)
|
|
458
|
+
|
|
459
|
+
if ctx.obj.get("view") == "json":
|
|
460
|
+
click.echo(
|
|
461
|
+
json.dumps(
|
|
462
|
+
{"success": True, "message": f"MCP '{mcp.name}' deleted"}, indent=2
|
|
463
|
+
)
|
|
464
|
+
)
|
|
465
|
+
else:
|
|
466
|
+
console.print(f"[green]✅ MCP '{mcp.name}' deleted successfully[/green]")
|
|
467
|
+
|
|
468
|
+
except Exception as e:
|
|
469
|
+
if ctx.obj.get("view") == "json":
|
|
470
|
+
click.echo(json.dumps({"error": str(e)}, indent=2))
|
|
471
|
+
else:
|
|
472
|
+
console.print(f"[red]Error deleting MCP: {e}[/red]")
|
|
473
|
+
raise click.ClickException(str(e))
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Language models commands.
|
|
2
|
+
|
|
3
|
+
Authors:
|
|
4
|
+
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
|
|
10
|
+
from ..utils import get_client, output_flags, output_list
|
|
11
|
+
|
|
12
|
+
console = Console()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@click.group(name="models", no_args_is_help=True)
|
|
16
|
+
def models_group():
|
|
17
|
+
"""Language model operations."""
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@models_group.command(name="list")
|
|
22
|
+
@output_flags()
|
|
23
|
+
@click.pass_context
|
|
24
|
+
def list_models(ctx):
|
|
25
|
+
"""List available language models."""
|
|
26
|
+
try:
|
|
27
|
+
client = get_client(ctx)
|
|
28
|
+
models = client.list_language_models()
|
|
29
|
+
|
|
30
|
+
# Define table columns: (data_key, header, style, width)
|
|
31
|
+
columns = [
|
|
32
|
+
("id", "ID", "dim", 36),
|
|
33
|
+
("provider", "Provider", "cyan", None),
|
|
34
|
+
("name", "Model", "green", None),
|
|
35
|
+
("base_url", "Base URL", "yellow", None),
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
# Transform function for safe dictionary access
|
|
39
|
+
def transform_model(model):
|
|
40
|
+
return {
|
|
41
|
+
"id": str(model.get("id", "N/A")),
|
|
42
|
+
"provider": model.get("provider", "N/A"),
|
|
43
|
+
"name": model.get("name", "N/A"),
|
|
44
|
+
"base_url": model.get("base_url", "Default") or "Default",
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
output_list(
|
|
48
|
+
ctx, models, "🧠Available Language Models", columns, transform_model
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
except Exception as e:
|
|
52
|
+
raise click.ClickException(str(e))
|