glaip-sdk 0.0.11__py3-none-any.whl → 0.0.13__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/mcps.py +296 -32
- 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/client/mcps.py +3 -3
- {glaip_sdk-0.0.11.dist-info → glaip_sdk-0.0.13.dist-info}/METADATA +1 -1
- {glaip_sdk-0.0.11.dist-info → glaip_sdk-0.0.13.dist-info}/RECORD +9 -6
- {glaip_sdk-0.0.11.dist-info → glaip_sdk-0.0.13.dist-info}/WHEEL +0 -0
- {glaip_sdk-0.0.11.dist-info → glaip_sdk-0.0.13.dist-info}/entry_points.txt +0 -0
glaip_sdk/cli/commands/mcps.py
CHANGED
|
@@ -24,7 +24,13 @@ from glaip_sdk.cli.display import (
|
|
|
24
24
|
)
|
|
25
25
|
from glaip_sdk.cli.io import (
|
|
26
26
|
fetch_raw_resource_details,
|
|
27
|
+
load_resource_from_file_with_validation,
|
|
27
28
|
)
|
|
29
|
+
from glaip_sdk.cli.mcp_validators import (
|
|
30
|
+
validate_mcp_auth_structure,
|
|
31
|
+
validate_mcp_config_structure,
|
|
32
|
+
)
|
|
33
|
+
from glaip_sdk.cli.parsers.json_input import parse_json_input
|
|
28
34
|
from glaip_sdk.cli.resolution import resolve_resource_reference
|
|
29
35
|
from glaip_sdk.cli.utils import (
|
|
30
36
|
coerce_to_row,
|
|
@@ -36,8 +42,12 @@ from glaip_sdk.cli.utils import (
|
|
|
36
42
|
output_result,
|
|
37
43
|
spinner_context,
|
|
38
44
|
)
|
|
45
|
+
from glaip_sdk.config.constants import (
|
|
46
|
+
DEFAULT_MCP_TYPE,
|
|
47
|
+
)
|
|
39
48
|
from glaip_sdk.rich_components import AIPPanel
|
|
40
49
|
from glaip_sdk.utils import format_datetime
|
|
50
|
+
from glaip_sdk.utils.import_export import convert_export_to_import_format
|
|
41
51
|
from glaip_sdk.utils.serialization import (
|
|
42
52
|
build_mcp_export_payload,
|
|
43
53
|
write_resource_export,
|
|
@@ -85,6 +95,180 @@ def _resolve_mcp(
|
|
|
85
95
|
)
|
|
86
96
|
|
|
87
97
|
|
|
98
|
+
def _strip_server_only_fields(import_data: dict[str, Any]) -> dict[str, Any]:
|
|
99
|
+
"""Remove fields that should not be forwarded during import-driven creation.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
import_data: Raw import payload loaded from disk.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
A shallow copy of the data with server-managed fields removed.
|
|
106
|
+
"""
|
|
107
|
+
cleaned = dict(import_data)
|
|
108
|
+
for key in (
|
|
109
|
+
"id",
|
|
110
|
+
"type",
|
|
111
|
+
"status",
|
|
112
|
+
"connection_status",
|
|
113
|
+
"created_at",
|
|
114
|
+
"updated_at",
|
|
115
|
+
):
|
|
116
|
+
cleaned.pop(key, None)
|
|
117
|
+
return cleaned
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _load_import_ready_payload(import_file: str) -> dict[str, Any]:
|
|
121
|
+
"""Load and normalise an imported MCP definition for create operations.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
import_file: Path to an MCP export file (JSON or YAML).
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
Normalised import payload ready for CLI/REST usage.
|
|
128
|
+
|
|
129
|
+
Raises:
|
|
130
|
+
click.ClickException: If the file cannot be parsed or validated.
|
|
131
|
+
"""
|
|
132
|
+
raw_data = load_resource_from_file_with_validation(Path(import_file), "MCP")
|
|
133
|
+
import_data = convert_export_to_import_format(raw_data)
|
|
134
|
+
import_data = _strip_server_only_fields(import_data)
|
|
135
|
+
|
|
136
|
+
transport = import_data.get("transport")
|
|
137
|
+
|
|
138
|
+
if "config" in import_data:
|
|
139
|
+
import_data["config"] = validate_mcp_config_structure(
|
|
140
|
+
import_data["config"],
|
|
141
|
+
transport=transport,
|
|
142
|
+
source="import file",
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
if "authentication" in import_data:
|
|
146
|
+
import_data["authentication"] = validate_mcp_auth_structure(
|
|
147
|
+
import_data["authentication"],
|
|
148
|
+
source="import file",
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
return import_data
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _coerce_cli_string(value: str | None) -> str | None:
|
|
155
|
+
"""Normalise CLI string values so blanks are treated as missing.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
value: User-provided string option.
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
The stripped string, or ``None`` when the value is blank/whitespace-only.
|
|
162
|
+
"""
|
|
163
|
+
if value is None:
|
|
164
|
+
return None
|
|
165
|
+
trimmed = value.strip()
|
|
166
|
+
# Treat whitespace-only strings as None
|
|
167
|
+
return trimmed if trimmed else None
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _merge_config_field(
|
|
171
|
+
merged_base: dict[str, Any],
|
|
172
|
+
cli_config: str | None,
|
|
173
|
+
final_transport: str | None,
|
|
174
|
+
) -> None:
|
|
175
|
+
"""Merge config field with validation.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
merged_base: Base payload to update in-place.
|
|
179
|
+
cli_config: Raw CLI JSON string for config.
|
|
180
|
+
final_transport: Transport type for validation.
|
|
181
|
+
|
|
182
|
+
Raises:
|
|
183
|
+
click.ClickException: If config JSON parsing or validation fails.
|
|
184
|
+
"""
|
|
185
|
+
if cli_config is not None:
|
|
186
|
+
parsed_config = parse_json_input(cli_config)
|
|
187
|
+
merged_base["config"] = validate_mcp_config_structure(
|
|
188
|
+
parsed_config,
|
|
189
|
+
transport=final_transport,
|
|
190
|
+
source="--config",
|
|
191
|
+
)
|
|
192
|
+
elif "config" not in merged_base or merged_base["config"] is None:
|
|
193
|
+
merged_base["config"] = {}
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _merge_auth_field(
|
|
197
|
+
merged_base: dict[str, Any],
|
|
198
|
+
cli_auth: str | None,
|
|
199
|
+
) -> None:
|
|
200
|
+
"""Merge authentication field with validation.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
merged_base: Base payload to update in-place.
|
|
204
|
+
cli_auth: Raw CLI JSON string for authentication.
|
|
205
|
+
|
|
206
|
+
Raises:
|
|
207
|
+
click.ClickException: If auth JSON parsing or validation fails.
|
|
208
|
+
"""
|
|
209
|
+
if cli_auth is not None:
|
|
210
|
+
parsed_auth = parse_json_input(cli_auth)
|
|
211
|
+
merged_base["authentication"] = validate_mcp_auth_structure(
|
|
212
|
+
parsed_auth,
|
|
213
|
+
source="--auth",
|
|
214
|
+
)
|
|
215
|
+
elif "authentication" not in merged_base:
|
|
216
|
+
merged_base["authentication"] = None
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def _merge_import_payload(
|
|
220
|
+
import_data: dict[str, Any] | None,
|
|
221
|
+
*,
|
|
222
|
+
cli_name: str | None,
|
|
223
|
+
cli_transport: str | None,
|
|
224
|
+
cli_description: str | None,
|
|
225
|
+
cli_config: str | None,
|
|
226
|
+
cli_auth: str | None,
|
|
227
|
+
) -> tuple[dict[str, Any], list[str]]:
|
|
228
|
+
"""Merge import data with CLI overrides while tracking missing fields.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
import_data: Normalised payload loaded from file (if provided).
|
|
232
|
+
cli_name: Name supplied via CLI option.
|
|
233
|
+
cli_transport: Transport supplied via CLI option.
|
|
234
|
+
cli_description: Description supplied via CLI option.
|
|
235
|
+
cli_config: Raw CLI JSON string for config.
|
|
236
|
+
cli_auth: Raw CLI JSON string for authentication.
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
A tuple of (merged_payload, missing_required_fields).
|
|
240
|
+
|
|
241
|
+
Raises:
|
|
242
|
+
click.ClickException: If config/auth JSON parsing or validation fails.
|
|
243
|
+
"""
|
|
244
|
+
merged_base = import_data.copy() if import_data else {}
|
|
245
|
+
|
|
246
|
+
# Merge simple string fields using truthy CLI overrides
|
|
247
|
+
for field, cli_value in (
|
|
248
|
+
("name", _coerce_cli_string(cli_name)),
|
|
249
|
+
("transport", _coerce_cli_string(cli_transport)),
|
|
250
|
+
("description", _coerce_cli_string(cli_description)),
|
|
251
|
+
):
|
|
252
|
+
if cli_value is not None:
|
|
253
|
+
merged_base[field] = cli_value
|
|
254
|
+
|
|
255
|
+
# Determine final transport before validating config
|
|
256
|
+
final_transport = merged_base.get("transport")
|
|
257
|
+
|
|
258
|
+
# Merge config and authentication with validation
|
|
259
|
+
_merge_config_field(merged_base, cli_config, final_transport)
|
|
260
|
+
_merge_auth_field(merged_base, cli_auth)
|
|
261
|
+
|
|
262
|
+
# Validate required fields
|
|
263
|
+
missing_fields = []
|
|
264
|
+
for required in ("name", "transport"):
|
|
265
|
+
value = merged_base.get(required)
|
|
266
|
+
if not isinstance(value, str) or not value.strip():
|
|
267
|
+
missing_fields.append(required)
|
|
268
|
+
|
|
269
|
+
return merged_base, missing_fields
|
|
270
|
+
|
|
271
|
+
|
|
88
272
|
@mcps_group.command(name="list")
|
|
89
273
|
@output_flags()
|
|
90
274
|
@click.pass_context
|
|
@@ -134,50 +318,114 @@ def list_mcps(ctx: Any) -> None:
|
|
|
134
318
|
|
|
135
319
|
|
|
136
320
|
@mcps_group.command()
|
|
137
|
-
@click.option("--name",
|
|
138
|
-
@click.option("--transport",
|
|
321
|
+
@click.option("--name", help="MCP name")
|
|
322
|
+
@click.option("--transport", help="MCP transport protocol")
|
|
139
323
|
@click.option("--description", help="MCP description")
|
|
140
|
-
@click.option(
|
|
324
|
+
@click.option(
|
|
325
|
+
"--config",
|
|
326
|
+
help="JSON configuration string or @file reference (e.g., @config.json)",
|
|
327
|
+
)
|
|
328
|
+
@click.option(
|
|
329
|
+
"--auth",
|
|
330
|
+
"--authentication",
|
|
331
|
+
"auth",
|
|
332
|
+
help="JSON authentication object or @file reference (e.g., @auth.json)",
|
|
333
|
+
)
|
|
334
|
+
@click.option(
|
|
335
|
+
"--import",
|
|
336
|
+
"import_file",
|
|
337
|
+
type=click.Path(exists=True, dir_okay=False),
|
|
338
|
+
help="Import MCP configuration from JSON or YAML export",
|
|
339
|
+
)
|
|
141
340
|
@output_flags()
|
|
142
341
|
@click.pass_context
|
|
143
342
|
def create(
|
|
144
|
-
ctx: Any,
|
|
343
|
+
ctx: Any,
|
|
344
|
+
name: str | None,
|
|
345
|
+
transport: str | None,
|
|
346
|
+
description: str | None,
|
|
347
|
+
config: str | None,
|
|
348
|
+
auth: str | None,
|
|
349
|
+
import_file: str | None,
|
|
145
350
|
) -> None:
|
|
146
351
|
"""Create a new MCP with specified configuration.
|
|
147
352
|
|
|
353
|
+
You can create an MCP by providing all parameters via CLI options, or by
|
|
354
|
+
importing from a file and optionally overriding specific fields.
|
|
355
|
+
|
|
148
356
|
Args:
|
|
149
357
|
ctx: Click context containing output format preferences
|
|
150
|
-
name: MCP name (required)
|
|
151
|
-
transport: MCP transport protocol (required)
|
|
358
|
+
name: MCP name (required unless provided via --import)
|
|
359
|
+
transport: MCP transport protocol (required unless provided via --import)
|
|
152
360
|
description: Optional MCP description
|
|
153
|
-
config: JSON configuration string
|
|
361
|
+
config: JSON configuration string or @file reference
|
|
362
|
+
auth: JSON authentication object or @file reference
|
|
363
|
+
import_file: Optional path to import configuration from export file.
|
|
364
|
+
CLI options override imported values.
|
|
154
365
|
|
|
155
366
|
Raises:
|
|
156
367
|
ClickException: If JSON parsing fails or API request fails
|
|
368
|
+
|
|
369
|
+
Examples:
|
|
370
|
+
Create from CLI options:
|
|
371
|
+
aip mcps create --name my-mcp --transport http --config '{"url": "https://api.example.com"}'
|
|
372
|
+
|
|
373
|
+
Import from file:
|
|
374
|
+
aip mcps create --import mcp-export.json
|
|
375
|
+
|
|
376
|
+
Import with overrides:
|
|
377
|
+
aip mcps create --import mcp-export.json --name new-name --transport sse
|
|
157
378
|
"""
|
|
158
379
|
try:
|
|
159
380
|
client = get_client(ctx)
|
|
160
381
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
382
|
+
import_payload = (
|
|
383
|
+
_load_import_ready_payload(import_file) if import_file is not None else None
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
merged_payload, missing_fields = _merge_import_payload(
|
|
387
|
+
import_payload,
|
|
388
|
+
cli_name=name,
|
|
389
|
+
cli_transport=transport,
|
|
390
|
+
cli_description=description,
|
|
391
|
+
cli_config=config,
|
|
392
|
+
cli_auth=auth,
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
if missing_fields:
|
|
396
|
+
raise click.ClickException(
|
|
397
|
+
"Missing required fields after combining import and CLI values: "
|
|
398
|
+
+ ", ".join(missing_fields)
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
effective_name = merged_payload["name"]
|
|
402
|
+
effective_transport = merged_payload["transport"]
|
|
403
|
+
effective_description = merged_payload.get("description")
|
|
404
|
+
effective_config = merged_payload.get("config") or {}
|
|
405
|
+
effective_auth = merged_payload.get("authentication")
|
|
168
406
|
|
|
169
407
|
with spinner_context(
|
|
170
408
|
ctx,
|
|
171
409
|
"[bold blue]Creating MCP…[/bold blue]",
|
|
172
410
|
console_override=console,
|
|
173
411
|
):
|
|
174
|
-
|
|
175
|
-
name
|
|
176
|
-
|
|
177
|
-
transport
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
412
|
+
create_kwargs: dict[str, Any] = {
|
|
413
|
+
"name": effective_name,
|
|
414
|
+
"config": effective_config,
|
|
415
|
+
"transport": effective_transport,
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if effective_description is not None:
|
|
419
|
+
create_kwargs["description"] = effective_description
|
|
420
|
+
|
|
421
|
+
if effective_auth:
|
|
422
|
+
create_kwargs["authentication"] = effective_auth
|
|
423
|
+
|
|
424
|
+
mcp_metadata = merged_payload.get("mcp_metadata")
|
|
425
|
+
if mcp_metadata is not None:
|
|
426
|
+
create_kwargs["mcp_metadata"] = mcp_metadata
|
|
427
|
+
|
|
428
|
+
mcp = client.mcps.create_mcp(**create_kwargs)
|
|
181
429
|
|
|
182
430
|
# Handle JSON output
|
|
183
431
|
handle_json_output(ctx, mcp.model_dump())
|
|
@@ -187,9 +435,9 @@ def create(
|
|
|
187
435
|
"MCP",
|
|
188
436
|
mcp.name,
|
|
189
437
|
mcp.id,
|
|
190
|
-
Type="
|
|
191
|
-
Transport=getattr(mcp, "transport",
|
|
192
|
-
Description=
|
|
438
|
+
Type=getattr(mcp, "type", DEFAULT_MCP_TYPE),
|
|
439
|
+
Transport=getattr(mcp, "transport", effective_transport),
|
|
440
|
+
Description=effective_description or "No description",
|
|
193
441
|
)
|
|
194
442
|
handle_rich_output(ctx, rich_panel)
|
|
195
443
|
|
|
@@ -437,7 +685,6 @@ def list_tools(ctx: Any, mcp_ref: str) -> None:
|
|
|
437
685
|
columns = [
|
|
438
686
|
("name", "Name", "cyan", None),
|
|
439
687
|
("description", "Description", "green", 50),
|
|
440
|
-
("type", "Type", "yellow", None),
|
|
441
688
|
]
|
|
442
689
|
|
|
443
690
|
# Transform function for safe dictionary access
|
|
@@ -447,7 +694,6 @@ def list_tools(ctx: Any, mcp_ref: str) -> None:
|
|
|
447
694
|
"description": tool.get("description", "N/A")[:47] + "..."
|
|
448
695
|
if len(tool.get("description", "")) > 47
|
|
449
696
|
else tool.get("description", "N/A"),
|
|
450
|
-
"type": tool.get("type", "N/A"),
|
|
451
697
|
}
|
|
452
698
|
|
|
453
699
|
output_list(
|
|
@@ -525,7 +771,16 @@ def connect(ctx: Any, config_file: str) -> None:
|
|
|
525
771
|
@click.argument("mcp_ref")
|
|
526
772
|
@click.option("--name", help="New MCP name")
|
|
527
773
|
@click.option("--description", help="New description")
|
|
528
|
-
@click.option(
|
|
774
|
+
@click.option(
|
|
775
|
+
"--config",
|
|
776
|
+
help="JSON configuration string or @file reference (e.g., @config.json)",
|
|
777
|
+
)
|
|
778
|
+
@click.option(
|
|
779
|
+
"--auth",
|
|
780
|
+
"--authentication",
|
|
781
|
+
"auth",
|
|
782
|
+
help="JSON authentication object or @file reference (e.g., @auth.json)",
|
|
783
|
+
)
|
|
529
784
|
@output_flags()
|
|
530
785
|
@click.pass_context
|
|
531
786
|
def update(
|
|
@@ -534,6 +789,7 @@ def update(
|
|
|
534
789
|
name: str | None,
|
|
535
790
|
description: str | None,
|
|
536
791
|
config: str | None,
|
|
792
|
+
auth: str | None,
|
|
537
793
|
) -> None:
|
|
538
794
|
"""Update an existing MCP with new configuration values.
|
|
539
795
|
|
|
@@ -542,7 +798,8 @@ def update(
|
|
|
542
798
|
mcp_ref: MCP reference (ID or name)
|
|
543
799
|
name: New MCP name (optional)
|
|
544
800
|
description: New description (optional)
|
|
545
|
-
config: New JSON configuration string (optional)
|
|
801
|
+
config: New JSON configuration string or @file reference (optional)
|
|
802
|
+
auth: New JSON authentication object or @file reference (optional)
|
|
546
803
|
|
|
547
804
|
Raises:
|
|
548
805
|
ClickException: If MCP not found, JSON invalid, or no fields specified
|
|
@@ -564,10 +821,17 @@ def update(
|
|
|
564
821
|
if description is not None:
|
|
565
822
|
update_data["description"] = description
|
|
566
823
|
if config is not None:
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
824
|
+
parsed_config = parse_json_input(config)
|
|
825
|
+
update_data["config"] = validate_mcp_config_structure(
|
|
826
|
+
parsed_config,
|
|
827
|
+
transport=getattr(mcp, "transport", None),
|
|
828
|
+
source="--config",
|
|
829
|
+
)
|
|
830
|
+
if auth is not None:
|
|
831
|
+
parsed_auth = parse_json_input(auth)
|
|
832
|
+
update_data["authentication"] = validate_mcp_auth_structure(
|
|
833
|
+
parsed_auth, source="--auth"
|
|
834
|
+
)
|
|
571
835
|
|
|
572
836
|
if not update_data:
|
|
573
837
|
raise click.ClickException("No update fields specified")
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
"""MCP configuration and authentication validation for CLI.
|
|
2
|
+
|
|
3
|
+
This module provides validation functions for MCP config and auth structures
|
|
4
|
+
that are used in CLI commands. It ensures data conforms to the MCP schema
|
|
5
|
+
documented in docs/reference/schemas/mcps.md.
|
|
6
|
+
|
|
7
|
+
Authors:
|
|
8
|
+
Putu Ravindra Wiguna (putu.r.wiguna@gdplabs.id)
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from typing import Any
|
|
12
|
+
from urllib.parse import urlparse
|
|
13
|
+
|
|
14
|
+
import click
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def format_validation_error(prefix: str, detail: str | None = None) -> str:
|
|
18
|
+
"""Format a validation error message with optional detail.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
prefix: Main error message
|
|
22
|
+
detail: Optional additional detail to append
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
Formatted error message string
|
|
26
|
+
|
|
27
|
+
Examples:
|
|
28
|
+
>>> format_validation_error("Invalid config", "Missing 'url' field")
|
|
29
|
+
"Invalid config\\nMissing 'url' field"
|
|
30
|
+
"""
|
|
31
|
+
parts = [prefix]
|
|
32
|
+
if detail:
|
|
33
|
+
parts.append(detail)
|
|
34
|
+
return "\n".join(parts)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def validate_mcp_config_structure(
|
|
38
|
+
config: Any, *, transport: str | None = None, source: str = "--config"
|
|
39
|
+
) -> dict[str, Any]:
|
|
40
|
+
"""Validate MCP configuration structure for CLI commands.
|
|
41
|
+
|
|
42
|
+
Validates that the config is a dictionary with a valid 'url' field.
|
|
43
|
+
The 'url' must be an absolute HTTP/HTTPS URL as required by the MCP schema.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
config: Configuration value to validate (expected to be a dict)
|
|
47
|
+
transport: Optional transport type ('http' or 'sse') for context in errors
|
|
48
|
+
source: Source parameter name for error messages (default: "--config")
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
Validated configuration dictionary
|
|
52
|
+
|
|
53
|
+
Raises:
|
|
54
|
+
click.ClickException: If config is not a dict, missing 'url', or URL is invalid
|
|
55
|
+
|
|
56
|
+
Examples:
|
|
57
|
+
>>> validate_mcp_config_structure({"url": "https://api.example.com"})
|
|
58
|
+
{'url': 'https://api.example.com'}
|
|
59
|
+
|
|
60
|
+
>>> validate_mcp_config_structure([1, 2, 3]) # doctest: +SKIP
|
|
61
|
+
ClickException: Invalid --config value
|
|
62
|
+
Expected a JSON object representing MCP configuration.
|
|
63
|
+
|
|
64
|
+
Schema Reference:
|
|
65
|
+
See docs/reference/schemas/mcps.md - Config Object Structure
|
|
66
|
+
- Required field: 'url' (string, must be valid HTTP/HTTPS URL)
|
|
67
|
+
- Additional fields allowed and passed through
|
|
68
|
+
"""
|
|
69
|
+
if not isinstance(config, dict):
|
|
70
|
+
raise click.ClickException(
|
|
71
|
+
format_validation_error(
|
|
72
|
+
f"Invalid {source} value",
|
|
73
|
+
"Expected a JSON object representing MCP configuration.",
|
|
74
|
+
)
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
url_value = config.get("url")
|
|
78
|
+
if not isinstance(url_value, str) or not url_value.strip():
|
|
79
|
+
requirement = "Missing required 'url' field with a non-empty string value."
|
|
80
|
+
if transport:
|
|
81
|
+
requirement += f" Required for transport '{transport}'."
|
|
82
|
+
raise click.ClickException(
|
|
83
|
+
format_validation_error(f"Invalid {source} value", requirement)
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
parsed_url = urlparse(url_value)
|
|
87
|
+
if parsed_url.scheme not in {"http", "https"} or not parsed_url.netloc:
|
|
88
|
+
raise click.ClickException(
|
|
89
|
+
format_validation_error(
|
|
90
|
+
f"Invalid {source} value",
|
|
91
|
+
"'url' must be an absolute HTTP or HTTPS URL.",
|
|
92
|
+
)
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
return config
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _validate_headers_mapping(
|
|
99
|
+
headers: Any, *, source: str, context: str
|
|
100
|
+
) -> dict[str, str]:
|
|
101
|
+
"""Validate headers mapping for authentication.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
headers: Headers value to validate (expected to be a non-empty dict)
|
|
105
|
+
source: Source parameter name for error messages
|
|
106
|
+
context: Context description for error messages (e.g., "bearer-token authentication")
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
Validated headers dictionary with string keys and values
|
|
110
|
+
|
|
111
|
+
Raises:
|
|
112
|
+
click.ClickException: If headers is not a dict, empty, or contains invalid entries
|
|
113
|
+
"""
|
|
114
|
+
if not isinstance(headers, dict) or not headers:
|
|
115
|
+
raise click.ClickException(
|
|
116
|
+
format_validation_error(
|
|
117
|
+
f"Invalid {source} value",
|
|
118
|
+
f"{context} must provide a non-empty 'headers' object with string keys and values.",
|
|
119
|
+
)
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
normalized: dict[str, str] = {}
|
|
123
|
+
for key, value in headers.items():
|
|
124
|
+
if not isinstance(key, str) or not key.strip():
|
|
125
|
+
raise click.ClickException(
|
|
126
|
+
format_validation_error(
|
|
127
|
+
f"Invalid {source} value",
|
|
128
|
+
"Header names must be non-empty strings.",
|
|
129
|
+
)
|
|
130
|
+
)
|
|
131
|
+
if not isinstance(value, str) or not value.strip():
|
|
132
|
+
raise click.ClickException(
|
|
133
|
+
format_validation_error(
|
|
134
|
+
f"Invalid {source} value",
|
|
135
|
+
f"Header '{key}' must have a non-empty string value.",
|
|
136
|
+
)
|
|
137
|
+
)
|
|
138
|
+
normalized[key] = value
|
|
139
|
+
return normalized
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _validate_bearer_token_auth(auth: dict[str, Any], source: str) -> dict[str, Any]:
|
|
143
|
+
"""Validate bearer-token authentication.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
auth: Authentication dictionary
|
|
147
|
+
source: Source parameter name for error messages
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
Validated bearer-token authentication dictionary
|
|
151
|
+
|
|
152
|
+
Raises:
|
|
153
|
+
click.ClickException: If bearer-token structure is invalid
|
|
154
|
+
"""
|
|
155
|
+
token = auth.get("token")
|
|
156
|
+
if isinstance(token, str) and token.strip():
|
|
157
|
+
return {"type": "bearer-token", "token": token}
|
|
158
|
+
headers = auth.get("headers")
|
|
159
|
+
normalized_headers = _validate_headers_mapping(
|
|
160
|
+
headers, source=source, context="bearer-token authentication"
|
|
161
|
+
)
|
|
162
|
+
return {"type": "bearer-token", "headers": normalized_headers}
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _validate_api_key_auth(auth: dict[str, Any], source: str) -> dict[str, Any]:
|
|
166
|
+
"""Validate api-key authentication.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
auth: Authentication dictionary
|
|
170
|
+
source: Source parameter name for error messages
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
Validated api-key authentication dictionary
|
|
174
|
+
|
|
175
|
+
Raises:
|
|
176
|
+
click.ClickException: If api-key structure is invalid
|
|
177
|
+
"""
|
|
178
|
+
headers = auth.get("headers")
|
|
179
|
+
if headers is not None:
|
|
180
|
+
normalized_headers = _validate_headers_mapping(
|
|
181
|
+
headers, source=source, context="api-key authentication"
|
|
182
|
+
)
|
|
183
|
+
return {"type": "api-key", "headers": normalized_headers}
|
|
184
|
+
|
|
185
|
+
key = auth.get("key")
|
|
186
|
+
value = auth.get("value")
|
|
187
|
+
if not isinstance(key, str) or not key.strip():
|
|
188
|
+
raise click.ClickException(
|
|
189
|
+
format_validation_error(
|
|
190
|
+
f"Invalid {source} value",
|
|
191
|
+
"api-key authentication requires a non-empty 'key'.",
|
|
192
|
+
)
|
|
193
|
+
)
|
|
194
|
+
if not isinstance(value, str) or not value.strip():
|
|
195
|
+
raise click.ClickException(
|
|
196
|
+
format_validation_error(
|
|
197
|
+
f"Invalid {source} value",
|
|
198
|
+
"api-key authentication requires a non-empty 'value'.",
|
|
199
|
+
)
|
|
200
|
+
)
|
|
201
|
+
return {"type": "api-key", "key": key, "value": value}
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _validate_custom_header_auth(auth: dict[str, Any], source: str) -> dict[str, Any]:
|
|
205
|
+
"""Validate custom-header authentication.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
auth: Authentication dictionary
|
|
209
|
+
source: Source parameter name for error messages
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
Validated custom-header authentication dictionary
|
|
213
|
+
|
|
214
|
+
Raises:
|
|
215
|
+
click.ClickException: If custom-header structure is invalid
|
|
216
|
+
"""
|
|
217
|
+
headers = auth.get("headers")
|
|
218
|
+
normalized_headers = _validate_headers_mapping(
|
|
219
|
+
headers, source=source, context="custom-header authentication"
|
|
220
|
+
)
|
|
221
|
+
return {"type": "custom-header", "headers": normalized_headers}
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def validate_mcp_auth_structure(auth: Any, *, source: str = "--auth") -> dict[str, Any]:
|
|
225
|
+
"""Validate MCP authentication structure for CLI commands.
|
|
226
|
+
|
|
227
|
+
Validates authentication objects according to the MCP schema, supporting:
|
|
228
|
+
- no-auth: No authentication required
|
|
229
|
+
- bearer-token: Bearer token via 'token' field or 'headers'
|
|
230
|
+
- api-key: API key via 'key'/'value' fields or 'headers'
|
|
231
|
+
- custom-header: Custom headers via 'headers' object
|
|
232
|
+
|
|
233
|
+
Args:
|
|
234
|
+
auth: Authentication value to validate (expected to be a dict or None)
|
|
235
|
+
source: Source parameter name for error messages (default: "--auth")
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
Validated authentication dictionary, or empty dict if auth is None
|
|
239
|
+
|
|
240
|
+
Raises:
|
|
241
|
+
click.ClickException: If auth structure is invalid or type is unsupported
|
|
242
|
+
|
|
243
|
+
Examples:
|
|
244
|
+
>>> validate_mcp_auth_structure(None)
|
|
245
|
+
{}
|
|
246
|
+
|
|
247
|
+
>>> validate_mcp_auth_structure({"type": "no-auth"})
|
|
248
|
+
{'type': 'no-auth'}
|
|
249
|
+
|
|
250
|
+
>>> validate_mcp_auth_structure({"type": "bearer-token", "token": "abc123"})
|
|
251
|
+
{'type': 'bearer-token', 'token': 'abc123'}
|
|
252
|
+
|
|
253
|
+
Schema Reference:
|
|
254
|
+
See docs/reference/schemas/mcps.md - Authentication Types
|
|
255
|
+
- Required field: 'type' (string, one of: no-auth, bearer-token, api-key, custom-header)
|
|
256
|
+
- Additional fields depend on type
|
|
257
|
+
"""
|
|
258
|
+
if auth is None:
|
|
259
|
+
return {}
|
|
260
|
+
|
|
261
|
+
if not isinstance(auth, dict):
|
|
262
|
+
raise click.ClickException(
|
|
263
|
+
format_validation_error(
|
|
264
|
+
f"Invalid {source} value",
|
|
265
|
+
"Expected a JSON object representing MCP authentication.",
|
|
266
|
+
)
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
raw_type = auth.get("type")
|
|
270
|
+
if not isinstance(raw_type, str) or not raw_type.strip():
|
|
271
|
+
raise click.ClickException(
|
|
272
|
+
format_validation_error(
|
|
273
|
+
f"Invalid {source} value",
|
|
274
|
+
"Authentication objects must include a non-empty 'type' field.",
|
|
275
|
+
)
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
auth_type = raw_type.strip()
|
|
279
|
+
|
|
280
|
+
# Dispatch to type-specific validators
|
|
281
|
+
if auth_type == "no-auth":
|
|
282
|
+
return {"type": "no-auth"}
|
|
283
|
+
if auth_type == "bearer-token":
|
|
284
|
+
return _validate_bearer_token_auth(auth, source)
|
|
285
|
+
if auth_type == "api-key":
|
|
286
|
+
return _validate_api_key_auth(auth, source)
|
|
287
|
+
if auth_type == "custom-header":
|
|
288
|
+
return _validate_custom_header_auth(auth, source)
|
|
289
|
+
|
|
290
|
+
# Unknown type
|
|
291
|
+
raise click.ClickException(
|
|
292
|
+
format_validation_error(
|
|
293
|
+
f"Invalid {source} value",
|
|
294
|
+
f"Unsupported authentication type '{auth_type}'. "
|
|
295
|
+
f"Supported types: no-auth, bearer-token, api-key, custom-header",
|
|
296
|
+
)
|
|
297
|
+
)
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""JSON input parser for CLI options.
|
|
2
|
+
|
|
3
|
+
Handles both inline JSON strings and @file references.
|
|
4
|
+
|
|
5
|
+
Authors:
|
|
6
|
+
Putu Ravindra Wiguna (putu.r.wiguna@gdplabs.id)
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import os
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
import click
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _format_file_error(
|
|
18
|
+
prefix: str, file_path_str: str, resolved_path: Path, *, detail: str | None = None
|
|
19
|
+
) -> str:
|
|
20
|
+
"""Format a file-related error message with path context.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
prefix: Main error message
|
|
24
|
+
file_path_str: Original file path string provided by user
|
|
25
|
+
resolved_path: Resolved absolute path
|
|
26
|
+
detail: Optional additional detail to append
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
Formatted error message string with file path context
|
|
30
|
+
|
|
31
|
+
Examples:
|
|
32
|
+
>>> from pathlib import Path
|
|
33
|
+
>>> _format_file_error("File not found", "config.json", Path("/abs/config.json"))
|
|
34
|
+
'File not found: config.json\\nResolved path: /abs/config.json'
|
|
35
|
+
"""
|
|
36
|
+
parts = [f"{prefix}: {file_path_str}", f"Resolved path: {resolved_path}"]
|
|
37
|
+
if detail:
|
|
38
|
+
parts.append(detail)
|
|
39
|
+
return "\n".join(parts)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _parse_json_from_file(file_path_str: str) -> Any:
|
|
43
|
+
"""Parse JSON from a file path.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
file_path_str: Path to the JSON file (without @ prefix).
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Parsed dictionary from file.
|
|
50
|
+
|
|
51
|
+
Raises:
|
|
52
|
+
click.ClickException: If file not found, not readable, empty, or invalid JSON.
|
|
53
|
+
"""
|
|
54
|
+
# Resolve relative paths against CWD
|
|
55
|
+
file_path = Path(file_path_str)
|
|
56
|
+
if not file_path.is_absolute():
|
|
57
|
+
file_path = Path.cwd() / file_path
|
|
58
|
+
|
|
59
|
+
# Check if file exists and is a regular file
|
|
60
|
+
if not file_path.is_file():
|
|
61
|
+
raise click.ClickException(
|
|
62
|
+
_format_file_error("File not found or not a file", file_path_str, file_path)
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# Check if file is readable
|
|
66
|
+
if not os.access(file_path, os.R_OK):
|
|
67
|
+
raise click.ClickException(
|
|
68
|
+
_format_file_error(
|
|
69
|
+
"File not readable (permission denied)", file_path_str, file_path
|
|
70
|
+
)
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
# Read file content
|
|
74
|
+
try:
|
|
75
|
+
content = file_path.read_text(encoding="utf-8")
|
|
76
|
+
except Exception as e:
|
|
77
|
+
raise click.ClickException(
|
|
78
|
+
_format_file_error(
|
|
79
|
+
"Error reading file", file_path_str, file_path, detail=f"Error: {e}"
|
|
80
|
+
)
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
# Check for empty content
|
|
84
|
+
if not content.strip():
|
|
85
|
+
raise click.ClickException(
|
|
86
|
+
_format_file_error("File is empty", file_path_str, file_path)
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
# Parse JSON from file content
|
|
90
|
+
try:
|
|
91
|
+
return json.loads(content)
|
|
92
|
+
except json.JSONDecodeError as e:
|
|
93
|
+
raise click.ClickException(
|
|
94
|
+
_format_file_error(
|
|
95
|
+
"Invalid JSON in file",
|
|
96
|
+
file_path_str,
|
|
97
|
+
file_path,
|
|
98
|
+
detail=f"Error: {e.msg} at line {e.lineno}, column {e.colno}",
|
|
99
|
+
)
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def parse_json_input(value: str | None) -> Any:
|
|
104
|
+
"""Parse JSON input from inline string or file reference.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
value: JSON string or @file reference. If None, returns None.
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
Parsed JSON value (dict, list, str, int, float, bool, None) or None if value is None.
|
|
111
|
+
|
|
112
|
+
Raises:
|
|
113
|
+
click.ClickException: If file not found, not readable, empty, or invalid JSON.
|
|
114
|
+
|
|
115
|
+
Examples:
|
|
116
|
+
>>> parse_json_input('{"key": "value"}')
|
|
117
|
+
{'key': 'value'}
|
|
118
|
+
|
|
119
|
+
>>> parse_json_input('@/path/to/config.json')
|
|
120
|
+
# Returns content of config.json parsed as JSON
|
|
121
|
+
|
|
122
|
+
>>> parse_json_input(None)
|
|
123
|
+
None
|
|
124
|
+
"""
|
|
125
|
+
if value is None:
|
|
126
|
+
return None
|
|
127
|
+
|
|
128
|
+
# Check if value is a file reference (strip whitespace first)
|
|
129
|
+
trimmed = value.strip()
|
|
130
|
+
if trimmed.startswith("@"):
|
|
131
|
+
return _parse_json_from_file(trimmed[1:])
|
|
132
|
+
|
|
133
|
+
# Parse inline JSON
|
|
134
|
+
try:
|
|
135
|
+
return json.loads(value)
|
|
136
|
+
except json.JSONDecodeError as e:
|
|
137
|
+
raise click.ClickException(
|
|
138
|
+
f"Invalid JSON in inline value\n"
|
|
139
|
+
f"Error: {e.msg} at line {e.lineno}, column {e.colno}"
|
|
140
|
+
)
|
glaip_sdk/client/mcps.py
CHANGED
|
@@ -57,7 +57,7 @@ class MCPClient(BaseClient):
|
|
|
57
57
|
def create_mcp(
|
|
58
58
|
self,
|
|
59
59
|
name: str,
|
|
60
|
-
description: str,
|
|
60
|
+
description: str | None = None,
|
|
61
61
|
config: dict[str, Any] | None = None,
|
|
62
62
|
**kwargs,
|
|
63
63
|
) -> MCP:
|
|
@@ -108,7 +108,7 @@ class MCPClient(BaseClient):
|
|
|
108
108
|
def _build_create_payload(
|
|
109
109
|
self,
|
|
110
110
|
name: str,
|
|
111
|
-
description: str,
|
|
111
|
+
description: str | None = None,
|
|
112
112
|
transport: str = DEFAULT_MCP_TRANSPORT,
|
|
113
113
|
config: dict[str, Any] | None = None,
|
|
114
114
|
**kwargs,
|
|
@@ -122,7 +122,7 @@ class MCPClient(BaseClient):
|
|
|
122
122
|
|
|
123
123
|
Args:
|
|
124
124
|
name: MCP name
|
|
125
|
-
description: MCP description
|
|
125
|
+
description: MCP description (optional)
|
|
126
126
|
transport: MCP transport protocol (defaults to stdio)
|
|
127
127
|
config: MCP configuration dictionary
|
|
128
128
|
**kwargs: Additional parameters
|
|
@@ -7,12 +7,15 @@ glaip_sdk/cli/auth.py,sha256=eYdtGmJ3XgiO96hq_69GF6b3W-aRWZrDQ-6bHuaRX4M,13517
|
|
|
7
7
|
glaip_sdk/cli/commands/__init__.py,sha256=x0CZlZbZHoHvuzfoTWIyEch6WmNnbPzxajrox6riYp0,173
|
|
8
8
|
glaip_sdk/cli/commands/agents.py,sha256=97dzowjHgk5knyHuI-0z2ojvqNlkebNN1-ikGEoS5sc,40623
|
|
9
9
|
glaip_sdk/cli/commands/configure.py,sha256=eRDzsaKV4fl2lJt8ieS4g2-xRnaa02eAAPW8xBf-tqA,7507
|
|
10
|
-
glaip_sdk/cli/commands/mcps.py,sha256=
|
|
10
|
+
glaip_sdk/cli/commands/mcps.py,sha256=ENhasfSupmCSKs-Ycg-M9Gy-58Y55SMIQzeg3fBJj48,28186
|
|
11
11
|
glaip_sdk/cli/commands/models.py,sha256=Ra3-50BPScNs0Q-j4b7U4iK0hNooucEyVgHpQ11-pt8,1700
|
|
12
12
|
glaip_sdk/cli/commands/tools.py,sha256=MOM9Db3HGL1stF-WvL5cZXjw-iZo2qc-oyKQHy6VwIM,18690
|
|
13
13
|
glaip_sdk/cli/display.py,sha256=jE20swoRKzpYUmc0jgbeonaXKeE9x95hfjWAEdnBYRc,8727
|
|
14
14
|
glaip_sdk/cli/io.py,sha256=GPkw3pQMLBGoD5GH-KlbKpNRlVWFZOXHE17F7V3kQsI,3343
|
|
15
15
|
glaip_sdk/cli/main.py,sha256=3Bl8u9t1MekzaNrAZqsx4TukbzzFdi6Wss6jvTDos00,12930
|
|
16
|
+
glaip_sdk/cli/mcp_validators.py,sha256=PEJRzb7ogRkwNJwJK9k5Xmb8hvoQ58L2Qywqd_3Wayo,10125
|
|
17
|
+
glaip_sdk/cli/parsers/__init__.py,sha256=Ycd4HDfYmA7GUGFt0ndBPBo5uTbv15XsXnYUj-a89ug,183
|
|
18
|
+
glaip_sdk/cli/parsers/json_input.py,sha256=iISa31ZsDNYWfCVRy0cifRIg2gjnhI-XtdDLB-UOshg,4039
|
|
16
19
|
glaip_sdk/cli/resolution.py,sha256=BOw2NchReLKewAwBAZLWw_3_bI7u3tfzQEO7kQbIiGE,2067
|
|
17
20
|
glaip_sdk/cli/slash/__init__.py,sha256=Vdv6Y8bu-pA8dxDlyP4XrhudBPivztUozhLAz9vaLig,682
|
|
18
21
|
glaip_sdk/cli/slash/agent_session.py,sha256=pDOwGXNHuyJIulrGYu1pacyF3oxHWeDQY-Uv92h2qVg,6859
|
|
@@ -25,7 +28,7 @@ glaip_sdk/client/__init__.py,sha256=nYLXfBVTTWwKjP0e63iumPYO4k5FifwWaELQPaPIKIg,
|
|
|
25
28
|
glaip_sdk/client/agents.py,sha256=FSKubF40wptMNIheC3_iawiX2CRbhTcNLFiz4qkPC6k,34659
|
|
26
29
|
glaip_sdk/client/base.py,sha256=O3dv3I7PqY91gH1FehBMRZcSXjwimfeBcJuiXidDmqw,13700
|
|
27
30
|
glaip_sdk/client/main.py,sha256=LlvYHP7-Hy7Eq1ep1kfk337K-Oue5SdKWJpqYfX9eXY,7993
|
|
28
|
-
glaip_sdk/client/mcps.py,sha256
|
|
31
|
+
glaip_sdk/client/mcps.py,sha256=-O-I15qjbwfSA69mouHY6g5_qgPWC4rM98VJLpOkh1A,8975
|
|
29
32
|
glaip_sdk/client/tools.py,sha256=n8DIiOOf1YU_j9JK3Bx2-rDnkpckPi0MI9Ok2s1kwa4,16634
|
|
30
33
|
glaip_sdk/client/validators.py,sha256=NtPsWjQLjj25LiUnmR-WuS8lL5p4MVRaYT9UVRmj9bo,8809
|
|
31
34
|
glaip_sdk/config/constants.py,sha256=ysEobMiXlLZGIOEaqTdHpPF8kmg5nbLn7BIcBvTCuHM,819
|
|
@@ -55,7 +58,7 @@ glaip_sdk/utils/rich_utils.py,sha256=-Ij-1bIJvnVAi6DrfftchIlMcvOTjVmSE0Qqax0EY_s
|
|
|
55
58
|
glaip_sdk/utils/run_renderer.py,sha256=d_VMI6LbvHPUUeRmGqh5wK_lHqDEIAcym2iqpbtDad0,1365
|
|
56
59
|
glaip_sdk/utils/serialization.py,sha256=T1yt_8G2DCFpcxx7XnqFl5slksRXfBCUuLQJTreGYEQ,11806
|
|
57
60
|
glaip_sdk/utils/validation.py,sha256=QNORcdyvuliEs4EH2_mkDgmoyT9utgl7YNhaf45SEf8,6992
|
|
58
|
-
glaip_sdk-0.0.
|
|
59
|
-
glaip_sdk-0.0.
|
|
60
|
-
glaip_sdk-0.0.
|
|
61
|
-
glaip_sdk-0.0.
|
|
61
|
+
glaip_sdk-0.0.13.dist-info/METADATA,sha256=Kv1zs8YO3gQigogChDeITrmnA7UggqkM_qBFnkAAi-k,4984
|
|
62
|
+
glaip_sdk-0.0.13.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
63
|
+
glaip_sdk-0.0.13.dist-info/entry_points.txt,sha256=EGs8NO8J1fdFMWA3CsF7sKBEvtHb_fujdCoNPhfMouE,47
|
|
64
|
+
glaip_sdk-0.0.13.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|