glaip-sdk 0.6.25__py3-none-any.whl → 0.7.0__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/commands/agents/__init__.py +119 -0
- glaip_sdk/cli/commands/agents/_common.py +561 -0
- glaip_sdk/cli/commands/agents/create.py +151 -0
- glaip_sdk/cli/commands/agents/delete.py +64 -0
- glaip_sdk/cli/commands/agents/get.py +89 -0
- glaip_sdk/cli/commands/agents/list.py +129 -0
- glaip_sdk/cli/commands/agents/run.py +264 -0
- glaip_sdk/cli/commands/agents/sync_langflow.py +72 -0
- glaip_sdk/cli/commands/agents/update.py +112 -0
- glaip_sdk/cli/commands/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/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/slash/tui/__init__.py +10 -1
- glaip_sdk/cli/slash/tui/context.py +51 -0
- glaip_sdk/cli/slash/tui/terminal.py +402 -0
- glaip_sdk/client/agents.py +1 -1
- glaip_sdk/client/main.py +1 -1
- glaip_sdk/client/mcps.py +44 -13
- glaip_sdk/client/payloads/agent/__init__.py +23 -0
- glaip_sdk/client/{_agent_payloads.py → payloads/agent/requests.py} +22 -47
- glaip_sdk/client/payloads/agent/responses.py +43 -0
- glaip_sdk/client/tools.py +52 -23
- glaip_sdk/registry/tool.py +193 -81
- glaip_sdk/tools/base.py +41 -10
- glaip_sdk/utils/import_resolver.py +40 -2
- {glaip_sdk-0.6.25.dist-info → glaip_sdk-0.7.0.dist-info}/METADATA +2 -2
- {glaip_sdk-0.6.25.dist-info → glaip_sdk-0.7.0.dist-info}/RECORD +51 -18
- glaip_sdk/cli/commands/agents.py +0 -1502
- glaip_sdk/cli/commands/mcps.py +0 -1355
- glaip_sdk/cli/commands/tools.py +0 -575
- /glaip_sdk/cli/commands/{transcripts.py → transcripts_original.py} +0 -0
- {glaip_sdk-0.6.25.dist-info → glaip_sdk-0.7.0.dist-info}/WHEEL +0 -0
- {glaip_sdk-0.6.25.dist-info → glaip_sdk-0.7.0.dist-info}/entry_points.txt +0 -0
- {glaip_sdk-0.6.25.dist-info → glaip_sdk-0.7.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""Delete 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 (
|
|
15
|
+
display_confirmation_prompt,
|
|
16
|
+
display_deletion_success,
|
|
17
|
+
handle_json_output,
|
|
18
|
+
handle_rich_output,
|
|
19
|
+
)
|
|
20
|
+
from glaip_sdk.cli.core.context import get_client
|
|
21
|
+
from glaip_sdk.cli.core.rendering import spinner_context
|
|
22
|
+
|
|
23
|
+
from ._common import _handle_cli_error, _resolve_mcp, console, mcps_group
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@mcps_group.command()
|
|
27
|
+
@click.argument("mcp_ref")
|
|
28
|
+
@click.option("-y", "--yes", is_flag=True, help="Skip confirmation")
|
|
29
|
+
@output_flags()
|
|
30
|
+
@click.pass_context
|
|
31
|
+
def delete(ctx: Any, mcp_ref: str, yes: bool) -> None:
|
|
32
|
+
"""Delete an MCP after confirmation.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
ctx: Click context containing output format preferences
|
|
36
|
+
mcp_ref: MCP reference (ID or name)
|
|
37
|
+
yes: Skip confirmation prompt if True
|
|
38
|
+
|
|
39
|
+
Raises:
|
|
40
|
+
ClickException: If MCP not found or deletion fails
|
|
41
|
+
|
|
42
|
+
Note:
|
|
43
|
+
Requires confirmation unless --yes flag is provided.
|
|
44
|
+
Deletion is permanent and cannot be undone.
|
|
45
|
+
"""
|
|
46
|
+
try:
|
|
47
|
+
client = get_client(ctx)
|
|
48
|
+
|
|
49
|
+
# Resolve MCP using helper function
|
|
50
|
+
mcp = _resolve_mcp(ctx, client, mcp_ref)
|
|
51
|
+
|
|
52
|
+
# Confirm deletion
|
|
53
|
+
if not yes and not display_confirmation_prompt("MCP", mcp.name):
|
|
54
|
+
return
|
|
55
|
+
|
|
56
|
+
with spinner_context(
|
|
57
|
+
ctx,
|
|
58
|
+
"[bold blue]Deleting MCP…[/bold blue]",
|
|
59
|
+
console_override=console,
|
|
60
|
+
):
|
|
61
|
+
client.mcps.delete_mcp(mcp.id)
|
|
62
|
+
|
|
63
|
+
handle_json_output(
|
|
64
|
+
ctx,
|
|
65
|
+
{
|
|
66
|
+
"success": True,
|
|
67
|
+
"message": f"MCP '{mcp.name}' deleted",
|
|
68
|
+
},
|
|
69
|
+
)
|
|
70
|
+
handle_rich_output(ctx, display_deletion_success("MCP", mcp.name))
|
|
71
|
+
|
|
72
|
+
except Exception as e:
|
|
73
|
+
_handle_cli_error(ctx, e, "MCP deletion")
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"""Get MCP command.
|
|
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
|
+
|
|
14
|
+
from glaip_sdk.branding import SUCCESS_STYLE, WARNING_STYLE
|
|
15
|
+
from glaip_sdk.cli.context import detect_export_format, output_flags
|
|
16
|
+
from glaip_sdk.cli.core.context import get_client
|
|
17
|
+
from glaip_sdk.cli.core.output import fetch_resource_for_export, format_datetime_fields, output_result
|
|
18
|
+
from glaip_sdk.cli.core.rendering import spinner_context
|
|
19
|
+
from glaip_sdk.cli.io import fetch_raw_resource_details
|
|
20
|
+
from glaip_sdk.cli.rich_helpers import print_markup
|
|
21
|
+
from glaip_sdk.utils.serialization import build_mcp_export_payload, write_resource_export
|
|
22
|
+
import sys
|
|
23
|
+
|
|
24
|
+
from ._common import _resolve_mcp, console, mcps_group
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _handle_mcp_export(
|
|
28
|
+
ctx: Any,
|
|
29
|
+
client: Any,
|
|
30
|
+
mcp: Any,
|
|
31
|
+
export_path: Path,
|
|
32
|
+
no_auth_prompt: bool,
|
|
33
|
+
auth_placeholder: str,
|
|
34
|
+
) -> None:
|
|
35
|
+
"""Handle MCP export to file with format detection and auth handling.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
ctx: Click context for spinner management
|
|
39
|
+
client: API client for fetching MCP details
|
|
40
|
+
mcp: MCP object to export
|
|
41
|
+
export_path: Target file path (format detected from extension)
|
|
42
|
+
no_auth_prompt: Skip interactive secret prompts if True
|
|
43
|
+
auth_placeholder: Placeholder text for missing secrets
|
|
44
|
+
|
|
45
|
+
Note:
|
|
46
|
+
Supports JSON (.json) and YAML (.yaml/.yml) export formats.
|
|
47
|
+
In interactive mode, prompts for secret values.
|
|
48
|
+
In non-interactive mode, uses placeholder values.
|
|
49
|
+
"""
|
|
50
|
+
# Auto-detect format from file extension
|
|
51
|
+
detected_format = detect_export_format(export_path)
|
|
52
|
+
|
|
53
|
+
# Always export comprehensive data - re-fetch with full details
|
|
54
|
+
mcp = fetch_resource_for_export(
|
|
55
|
+
ctx,
|
|
56
|
+
mcp,
|
|
57
|
+
resource_type="MCP",
|
|
58
|
+
get_by_id_func=client.mcps.get_mcp_by_id,
|
|
59
|
+
console_override=console,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# Determine if we should prompt for secrets
|
|
63
|
+
prompt_for_secrets = not no_auth_prompt and sys.stdin.isatty()
|
|
64
|
+
|
|
65
|
+
# Warn user if non-interactive mode forces placeholder usage
|
|
66
|
+
if not no_auth_prompt and not sys.stdin.isatty():
|
|
67
|
+
print_markup(
|
|
68
|
+
f"[{WARNING_STYLE}]⚠️ Non-interactive mode detected. Using placeholder values for secrets.[/]",
|
|
69
|
+
console=console,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# Build and write export payload
|
|
73
|
+
if prompt_for_secrets:
|
|
74
|
+
# Interactive mode: no spinner during prompts
|
|
75
|
+
export_payload = build_mcp_export_payload(
|
|
76
|
+
mcp,
|
|
77
|
+
prompt_for_secrets=prompt_for_secrets,
|
|
78
|
+
placeholder=auth_placeholder,
|
|
79
|
+
console=console,
|
|
80
|
+
)
|
|
81
|
+
with spinner_context(
|
|
82
|
+
ctx,
|
|
83
|
+
"[bold blue]Writing export file…[/bold blue]",
|
|
84
|
+
console_override=console,
|
|
85
|
+
):
|
|
86
|
+
write_resource_export(export_path, export_payload, detected_format)
|
|
87
|
+
else:
|
|
88
|
+
# Non-interactive mode: spinner for entire export process
|
|
89
|
+
with spinner_context(
|
|
90
|
+
ctx,
|
|
91
|
+
"[bold blue]Exporting MCP configuration…[/bold blue]",
|
|
92
|
+
console_override=console,
|
|
93
|
+
):
|
|
94
|
+
export_payload = build_mcp_export_payload(
|
|
95
|
+
mcp,
|
|
96
|
+
prompt_for_secrets=prompt_for_secrets,
|
|
97
|
+
placeholder=auth_placeholder,
|
|
98
|
+
console=console,
|
|
99
|
+
)
|
|
100
|
+
write_resource_export(export_path, export_payload, detected_format)
|
|
101
|
+
|
|
102
|
+
print_markup(
|
|
103
|
+
f"[{SUCCESS_STYLE}]✅ Complete MCP configuration exported to: {export_path} (format: {detected_format})[/]",
|
|
104
|
+
console=console,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _display_mcp_details(ctx: Any, client: Any, mcp: Any) -> None:
|
|
109
|
+
"""Display MCP details using raw API data or fallback to Pydantic model.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
ctx: Click context containing output format preferences
|
|
113
|
+
client: API client for fetching raw MCP data
|
|
114
|
+
mcp: MCP object to display details for
|
|
115
|
+
|
|
116
|
+
Note:
|
|
117
|
+
Attempts to fetch raw API data first to preserve all fields.
|
|
118
|
+
Falls back to Pydantic model data if raw data unavailable.
|
|
119
|
+
Formats datetime fields for better readability.
|
|
120
|
+
"""
|
|
121
|
+
# Try to fetch raw API data first to preserve ALL fields
|
|
122
|
+
with spinner_context(
|
|
123
|
+
ctx,
|
|
124
|
+
"[bold blue]Fetching detailed MCP data…[/bold blue]",
|
|
125
|
+
console_override=console,
|
|
126
|
+
):
|
|
127
|
+
raw_mcp_data = fetch_raw_resource_details(client, mcp, "mcps")
|
|
128
|
+
|
|
129
|
+
if raw_mcp_data:
|
|
130
|
+
# Use raw API data - this preserves ALL fields
|
|
131
|
+
formatted_data = format_datetime_fields(raw_mcp_data)
|
|
132
|
+
|
|
133
|
+
output_result(
|
|
134
|
+
ctx,
|
|
135
|
+
formatted_data,
|
|
136
|
+
title="MCP Details",
|
|
137
|
+
panel_title=f"🔌 {raw_mcp_data.get('name', 'Unknown')}",
|
|
138
|
+
)
|
|
139
|
+
else:
|
|
140
|
+
# Fall back to Pydantic model data
|
|
141
|
+
console.print(f"[{WARNING_STYLE}]Falling back to Pydantic model data[/]")
|
|
142
|
+
result_data = {
|
|
143
|
+
"id": str(getattr(mcp, "id", "N/A")),
|
|
144
|
+
"name": getattr(mcp, "name", "N/A"),
|
|
145
|
+
"type": getattr(mcp, "type", "N/A"),
|
|
146
|
+
"config": getattr(mcp, "config", "N/A"),
|
|
147
|
+
"status": getattr(mcp, "status", "N/A"),
|
|
148
|
+
"connection_status": getattr(mcp, "connection_status", "N/A"),
|
|
149
|
+
}
|
|
150
|
+
output_result(ctx, result_data, title=f"🔌 {mcp.name}")
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@mcps_group.command()
|
|
154
|
+
@click.argument("mcp_ref")
|
|
155
|
+
@click.option(
|
|
156
|
+
"--export",
|
|
157
|
+
type=click.Path(dir_okay=False, writable=True),
|
|
158
|
+
help="Export complete MCP configuration to file (format auto-detected from .json/.yaml extension)",
|
|
159
|
+
)
|
|
160
|
+
@click.option(
|
|
161
|
+
"--no-auth-prompt",
|
|
162
|
+
is_flag=True,
|
|
163
|
+
help="Skip interactive secret prompts and use placeholder values.",
|
|
164
|
+
)
|
|
165
|
+
@click.option(
|
|
166
|
+
"--auth-placeholder",
|
|
167
|
+
default="<INSERT VALUE>",
|
|
168
|
+
show_default=True,
|
|
169
|
+
help="Placeholder text used when secrets are unavailable.",
|
|
170
|
+
)
|
|
171
|
+
@output_flags()
|
|
172
|
+
@click.pass_context
|
|
173
|
+
def get(
|
|
174
|
+
ctx: Any,
|
|
175
|
+
mcp_ref: str,
|
|
176
|
+
export: str | None,
|
|
177
|
+
no_auth_prompt: bool,
|
|
178
|
+
auth_placeholder: str,
|
|
179
|
+
) -> None:
|
|
180
|
+
r"""Get MCP details and optionally export configuration to file.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
ctx: Click context containing output format preferences
|
|
184
|
+
mcp_ref: MCP reference (ID or name)
|
|
185
|
+
export: Optional file path to export MCP configuration
|
|
186
|
+
no_auth_prompt: Skip interactive secret prompts if True
|
|
187
|
+
auth_placeholder: Placeholder text for missing secrets
|
|
188
|
+
|
|
189
|
+
Raises:
|
|
190
|
+
ClickException: If MCP not found or export fails
|
|
191
|
+
|
|
192
|
+
\b
|
|
193
|
+
Examples:
|
|
194
|
+
aip mcps get my-mcp
|
|
195
|
+
aip mcps get my-mcp --export mcp.json # Export as JSON
|
|
196
|
+
aip mcps get my-mcp --export mcp.yaml # Export as YAML
|
|
197
|
+
"""
|
|
198
|
+
try:
|
|
199
|
+
client = get_client(ctx)
|
|
200
|
+
|
|
201
|
+
# Resolve MCP using helper function
|
|
202
|
+
mcp = _resolve_mcp(ctx, client, mcp_ref)
|
|
203
|
+
|
|
204
|
+
# Handle export option
|
|
205
|
+
if export:
|
|
206
|
+
_handle_mcp_export(ctx, client, mcp, Path(export), no_auth_prompt, auth_placeholder)
|
|
207
|
+
|
|
208
|
+
# Display MCP details
|
|
209
|
+
_display_mcp_details(ctx, client, mcp)
|
|
210
|
+
|
|
211
|
+
except Exception as e:
|
|
212
|
+
raise click.ClickException(str(e)) from e
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""List MCPs 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.branding import ACCENT_STYLE, INFO
|
|
14
|
+
from glaip_sdk.cli.context import output_flags
|
|
15
|
+
from glaip_sdk.cli.core.output import coerce_to_row, output_list
|
|
16
|
+
from glaip_sdk.cli.core.rendering import with_client_and_spinner
|
|
17
|
+
|
|
18
|
+
from ._common import console, mcps_group
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@mcps_group.command(name="list")
|
|
22
|
+
@output_flags()
|
|
23
|
+
@click.pass_context
|
|
24
|
+
def list_mcps(ctx: Any) -> None:
|
|
25
|
+
"""List all MCPs in a formatted table.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
ctx: Click context containing output format preferences
|
|
29
|
+
|
|
30
|
+
Raises:
|
|
31
|
+
ClickException: If API request fails
|
|
32
|
+
"""
|
|
33
|
+
try:
|
|
34
|
+
with with_client_and_spinner(
|
|
35
|
+
ctx,
|
|
36
|
+
"[bold blue]Fetching MCPs…[/bold blue]",
|
|
37
|
+
console_override=console,
|
|
38
|
+
) as client:
|
|
39
|
+
mcps = client.mcps.list_mcps()
|
|
40
|
+
|
|
41
|
+
# Define table columns: (data_key, header, style, width)
|
|
42
|
+
columns = [
|
|
43
|
+
("id", "ID", "dim", 36),
|
|
44
|
+
("name", "Name", ACCENT_STYLE, None),
|
|
45
|
+
("config", "Config", INFO, None),
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
# Transform function for safe dictionary access
|
|
49
|
+
def transform_mcp(mcp: Any) -> dict[str, Any]:
|
|
50
|
+
"""Transform an MCP object to a display row dictionary.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
mcp: MCP object to transform.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
Dictionary with id, name, and config fields.
|
|
57
|
+
"""
|
|
58
|
+
row = coerce_to_row(mcp, ["id", "name", "config"])
|
|
59
|
+
# Ensure id is always a string
|
|
60
|
+
row["id"] = str(row["id"])
|
|
61
|
+
# Truncate config field for display
|
|
62
|
+
if row["config"] != "N/A":
|
|
63
|
+
row["config"] = str(row["config"])[:50] + "..." if len(str(row["config"])) > 50 else str(row["config"])
|
|
64
|
+
return row
|
|
65
|
+
|
|
66
|
+
output_list(ctx, mcps, "🔌 Available MCPs", columns, transform_mcp)
|
|
67
|
+
|
|
68
|
+
except Exception as e:
|
|
69
|
+
raise click.ClickException(str(e)) from e
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"""List MCP tools command.
|
|
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
|
+
|
|
14
|
+
from glaip_sdk.branding import ACCENT_STYLE, INFO
|
|
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.output import output_list
|
|
18
|
+
from glaip_sdk.cli.core.rendering import spinner_context
|
|
19
|
+
from glaip_sdk.cli.display import handle_json_output
|
|
20
|
+
from glaip_sdk.cli.io import load_resource_from_file_with_validation
|
|
21
|
+
from glaip_sdk.cli.mcp_validators import validate_mcp_config_structure
|
|
22
|
+
from glaip_sdk.icons import ICON_TOOL
|
|
23
|
+
|
|
24
|
+
from ._common import _resolve_mcp, console, mcps_group
|
|
25
|
+
|
|
26
|
+
MAX_DESCRIPTION_LEN = 50
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _get_tools_from_config(ctx: Any, client: Any, config_file: str) -> tuple[list[dict[str, Any]], str]:
|
|
30
|
+
"""Get tools from MCP config file.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
ctx: Click context
|
|
34
|
+
client: GlaIP client instance
|
|
35
|
+
config_file: Path to config file
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Tuple of (tools list, title string)
|
|
39
|
+
"""
|
|
40
|
+
config_data = load_resource_from_file_with_validation(Path(config_file), "MCP config")
|
|
41
|
+
|
|
42
|
+
# Validate config structure
|
|
43
|
+
transport = config_data.get("transport")
|
|
44
|
+
if "config" not in config_data:
|
|
45
|
+
raise click.ClickException("Invalid MCP config: missing 'config' section in the file.")
|
|
46
|
+
config_data["config"] = validate_mcp_config_structure(
|
|
47
|
+
config_data["config"],
|
|
48
|
+
transport=transport,
|
|
49
|
+
source=config_file,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
# Get tools from config without saving
|
|
53
|
+
with spinner_context(
|
|
54
|
+
ctx,
|
|
55
|
+
"[bold blue]Fetching tools from config…[/bold blue]",
|
|
56
|
+
console_override=console,
|
|
57
|
+
):
|
|
58
|
+
tools = client.mcps.get_mcp_tools_from_config(config_data)
|
|
59
|
+
|
|
60
|
+
title = f"{ICON_TOOL} Tools from config: {Path(config_file).name}"
|
|
61
|
+
return tools, title
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _get_tools_from_mcp(ctx: Any, client: Any, mcp_ref: str | None) -> tuple[list[dict[str, Any]], str]:
|
|
65
|
+
"""Get tools from saved MCP.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
ctx: Click context
|
|
69
|
+
client: GlaIP client instance
|
|
70
|
+
mcp_ref: MCP reference (ID or name)
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
Tuple of (tools list, title string)
|
|
74
|
+
"""
|
|
75
|
+
mcp = _resolve_mcp(ctx, client, mcp_ref)
|
|
76
|
+
|
|
77
|
+
with spinner_context(
|
|
78
|
+
ctx,
|
|
79
|
+
"[bold blue]Fetching MCP tools…[/bold blue]",
|
|
80
|
+
console_override=console,
|
|
81
|
+
):
|
|
82
|
+
tools = client.mcps.get_mcp_tools(mcp.id)
|
|
83
|
+
|
|
84
|
+
title = f"{ICON_TOOL} Tools from MCP: {mcp.name}"
|
|
85
|
+
return tools, title
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _output_tool_names(ctx: Any, tools: list[dict[str, Any]]) -> None:
|
|
89
|
+
"""Output only tool names.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
ctx: Click context
|
|
93
|
+
tools: List of tool dictionaries
|
|
94
|
+
"""
|
|
95
|
+
view = get_ctx_value(ctx, "view", "rich")
|
|
96
|
+
tool_names = [tool.get("name", "N/A") for tool in tools]
|
|
97
|
+
|
|
98
|
+
if view == "json":
|
|
99
|
+
handle_json_output(ctx, tool_names)
|
|
100
|
+
elif view == "plain":
|
|
101
|
+
if tool_names:
|
|
102
|
+
for name in tool_names:
|
|
103
|
+
console.print(name, markup=False)
|
|
104
|
+
console.print(f"Total: {len(tool_names)} tools", markup=False)
|
|
105
|
+
else:
|
|
106
|
+
console.print("No tools found", markup=False)
|
|
107
|
+
else:
|
|
108
|
+
if tool_names:
|
|
109
|
+
for name in tool_names:
|
|
110
|
+
console.print(name)
|
|
111
|
+
console.print(f"[dim]Total: {len(tool_names)} tools[/dim]")
|
|
112
|
+
else:
|
|
113
|
+
console.print("[yellow]No tools found[/yellow]")
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _transform_tool(tool: dict[str, Any]) -> dict[str, Any]:
|
|
117
|
+
"""Transform a tool dictionary to a display row dictionary.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
tool: Tool dictionary to transform.
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
Dictionary with name and description fields.
|
|
124
|
+
"""
|
|
125
|
+
description = tool.get("description", "N/A")
|
|
126
|
+
if len(description) > MAX_DESCRIPTION_LEN:
|
|
127
|
+
description = description[: MAX_DESCRIPTION_LEN - 3] + "..."
|
|
128
|
+
return {
|
|
129
|
+
"name": tool.get("name", "N/A"),
|
|
130
|
+
"description": description,
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _output_tools_table(ctx: Any, tools: list[dict[str, Any]], title: str) -> None:
|
|
135
|
+
"""Output tools in table format.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
ctx: Click context
|
|
139
|
+
tools: List of tool dictionaries
|
|
140
|
+
title: Table title
|
|
141
|
+
"""
|
|
142
|
+
columns = [
|
|
143
|
+
("name", "Name", ACCENT_STYLE, None),
|
|
144
|
+
("description", "Description", INFO, 50),
|
|
145
|
+
]
|
|
146
|
+
|
|
147
|
+
output_list(
|
|
148
|
+
ctx,
|
|
149
|
+
tools,
|
|
150
|
+
title,
|
|
151
|
+
columns,
|
|
152
|
+
_transform_tool,
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _validate_tool_command_args(mcp_ref: str | None, config_file: str | None) -> None:
|
|
157
|
+
"""Validate that exactly one of mcp_ref or config_file is provided.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
mcp_ref: MCP reference (ID or name)
|
|
161
|
+
config_file: Path to config file
|
|
162
|
+
|
|
163
|
+
Raises:
|
|
164
|
+
ClickException: If validation fails
|
|
165
|
+
"""
|
|
166
|
+
if not mcp_ref and not config_file:
|
|
167
|
+
raise click.ClickException(
|
|
168
|
+
"Either MCP_REF or --from-config must be provided.\n"
|
|
169
|
+
"Examples:\n"
|
|
170
|
+
" aip mcps tools <MCP_ID>\n"
|
|
171
|
+
" aip mcps tools --from-config mcp-config.json"
|
|
172
|
+
)
|
|
173
|
+
if mcp_ref and config_file:
|
|
174
|
+
raise click.ClickException(
|
|
175
|
+
"Cannot use both MCP_REF and --from-config at the same time.\n"
|
|
176
|
+
"Use either:\n"
|
|
177
|
+
" aip mcps tools <MCP_ID>\n"
|
|
178
|
+
" aip mcps tools --from-config mcp-config.json"
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
@mcps_group.command("tools")
|
|
183
|
+
@click.argument("mcp_ref", required=False)
|
|
184
|
+
@click.option(
|
|
185
|
+
"--from-config",
|
|
186
|
+
"--config",
|
|
187
|
+
"config_file",
|
|
188
|
+
type=click.Path(exists=True, dir_okay=False),
|
|
189
|
+
help="Get tools from MCP config file without saving to DB (JSON or YAML)",
|
|
190
|
+
)
|
|
191
|
+
@click.option(
|
|
192
|
+
"--names-only",
|
|
193
|
+
is_flag=True,
|
|
194
|
+
help="Show only tool names (useful for allowed_tools config)",
|
|
195
|
+
)
|
|
196
|
+
@output_flags()
|
|
197
|
+
@click.pass_context
|
|
198
|
+
def list_tools(ctx: Any, mcp_ref: str | None, config_file: str | None, names_only: bool) -> None:
|
|
199
|
+
"""List tools available from a specific MCP or config file.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
ctx: Click context containing output format preferences
|
|
203
|
+
mcp_ref: MCP reference (ID or name) - required if --from-config not used
|
|
204
|
+
config_file: Path to MCP config file - alternative to mcp_ref
|
|
205
|
+
names_only: Show only tool names instead of full table
|
|
206
|
+
|
|
207
|
+
Raises:
|
|
208
|
+
ClickException: If MCP not found or tools fetch fails
|
|
209
|
+
|
|
210
|
+
Examples:
|
|
211
|
+
Get tools from saved MCP:
|
|
212
|
+
aip mcps tools <MCP_ID>
|
|
213
|
+
|
|
214
|
+
Get tools from config file (without saving to DB):
|
|
215
|
+
aip mcps tools --from-config mcp-config.json
|
|
216
|
+
|
|
217
|
+
Get just tool names for allowed_tools config:
|
|
218
|
+
aip mcps tools <MCP_ID> --names-only
|
|
219
|
+
"""
|
|
220
|
+
try:
|
|
221
|
+
_validate_tool_command_args(mcp_ref, config_file)
|
|
222
|
+
client = get_client(ctx)
|
|
223
|
+
|
|
224
|
+
if config_file:
|
|
225
|
+
tools, title = _get_tools_from_config(ctx, client, config_file)
|
|
226
|
+
else:
|
|
227
|
+
tools, title = _get_tools_from_mcp(ctx, client, mcp_ref)
|
|
228
|
+
|
|
229
|
+
if names_only:
|
|
230
|
+
_output_tool_names(ctx, tools)
|
|
231
|
+
else:
|
|
232
|
+
_output_tools_table(ctx, tools, title)
|
|
233
|
+
|
|
234
|
+
except Exception as e:
|
|
235
|
+
raise click.ClickException(str(e)) from e
|