glaip-sdk 0.0.12__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.
@@ -24,6 +24,7 @@ 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
  )
28
29
  from glaip_sdk.cli.mcp_validators import (
29
30
  validate_mcp_auth_structure,
@@ -41,8 +42,12 @@ from glaip_sdk.cli.utils import (
41
42
  output_result,
42
43
  spinner_context,
43
44
  )
45
+ from glaip_sdk.config.constants import (
46
+ DEFAULT_MCP_TYPE,
47
+ )
44
48
  from glaip_sdk.rich_components import AIPPanel
45
49
  from glaip_sdk.utils import format_datetime
50
+ from glaip_sdk.utils.import_export import convert_export_to_import_format
46
51
  from glaip_sdk.utils.serialization import (
47
52
  build_mcp_export_payload,
48
53
  write_resource_export,
@@ -90,6 +95,180 @@ def _resolve_mcp(
90
95
  )
91
96
 
92
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
+
93
272
  @mcps_group.command(name="list")
94
273
  @output_flags()
95
274
  @click.pass_context
@@ -139,8 +318,8 @@ def list_mcps(ctx: Any) -> None:
139
318
 
140
319
 
141
320
  @mcps_group.command()
142
- @click.option("--name", required=True, help="MCP name")
143
- @click.option("--transport", required=True, help="MCP transport protocol")
321
+ @click.option("--name", help="MCP name")
322
+ @click.option("--transport", help="MCP transport protocol")
144
323
  @click.option("--description", help="MCP description")
145
324
  @click.option(
146
325
  "--config",
@@ -152,72 +331,100 @@ def list_mcps(ctx: Any) -> None:
152
331
  "auth",
153
332
  help="JSON authentication object or @file reference (e.g., @auth.json)",
154
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
+ )
155
340
  @output_flags()
156
341
  @click.pass_context
157
342
  def create(
158
343
  ctx: Any,
159
- name: str,
160
- transport: str,
344
+ name: str | None,
345
+ transport: str | None,
161
346
  description: str | None,
162
347
  config: str | None,
163
348
  auth: str | None,
349
+ import_file: str | None,
164
350
  ) -> None:
165
351
  """Create a new MCP with specified configuration.
166
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
+
167
356
  Args:
168
357
  ctx: Click context containing output format preferences
169
- name: MCP name (required)
170
- transport: MCP transport protocol (required)
358
+ name: MCP name (required unless provided via --import)
359
+ transport: MCP transport protocol (required unless provided via --import)
171
360
  description: Optional MCP description
172
361
  config: JSON configuration string or @file reference
173
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.
174
365
 
175
366
  Raises:
176
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
177
378
  """
178
379
  try:
179
380
  client = get_client(ctx)
180
381
 
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
382
+ import_payload = (
383
+ _load_import_ready_payload(import_file) if import_file is not None else None
198
384
  )
199
385
 
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
- }
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
+ )
207
394
 
208
- # Only add description if provided
209
- if description is not None:
210
- create_kwargs["description"] = description
395
+ if missing_fields:
396
+ raise click.ClickException(
397
+ "Missing required fields after combining import and CLI values: "
398
+ + ", ".join(missing_fields)
399
+ )
211
400
 
212
- # Only add authentication if provided
213
- if validated_auth:
214
- create_kwargs["authentication"] = validated_auth
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")
215
406
 
216
407
  with spinner_context(
217
408
  ctx,
218
409
  "[bold blue]Creating MCP…[/bold blue]",
219
410
  console_override=console,
220
411
  ):
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
+
221
428
  mcp = client.mcps.create_mcp(**create_kwargs)
222
429
 
223
430
  # Handle JSON output
@@ -228,9 +435,9 @@ def create(
228
435
  "MCP",
229
436
  mcp.name,
230
437
  mcp.id,
231
- Type="server",
232
- Transport=getattr(mcp, "transport", transport),
233
- Description=description or "No description",
438
+ Type=getattr(mcp, "type", DEFAULT_MCP_TYPE),
439
+ Transport=getattr(mcp, "transport", effective_transport),
440
+ Description=effective_description or "No description",
234
441
  )
235
442
  handle_rich_output(ctx, rich_panel)
236
443
 
@@ -478,7 +685,6 @@ def list_tools(ctx: Any, mcp_ref: str) -> None:
478
685
  columns = [
479
686
  ("name", "Name", "cyan", None),
480
687
  ("description", "Description", "green", 50),
481
- ("type", "Type", "yellow", None),
482
688
  ]
483
689
 
484
690
  # Transform function for safe dictionary access
@@ -488,7 +694,6 @@ def list_tools(ctx: Any, mcp_ref: str) -> None:
488
694
  "description": tool.get("description", "N/A")[:47] + "..."
489
695
  if len(tool.get("description", "")) > 47
490
696
  else tool.get("description", "N/A"),
491
- "type": tool.get("type", "N/A"),
492
697
  }
493
698
 
494
699
  output_list(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: glaip-sdk
3
- Version: 0.0.12
3
+ Version: 0.0.13
4
4
  Summary: Python SDK for GL AIP (GDP Labs AI Agent Package) - Simplified CLI Design
5
5
  License: MIT
6
6
  Author: Raymond Christopher
@@ -7,7 +7,7 @@ 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=_zBT-8LMg8DTkiMO0d63tUqjy36-bdqrjxZX7bM-vpg,21514
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
@@ -58,7 +58,7 @@ glaip_sdk/utils/rich_utils.py,sha256=-Ij-1bIJvnVAi6DrfftchIlMcvOTjVmSE0Qqax0EY_s
58
58
  glaip_sdk/utils/run_renderer.py,sha256=d_VMI6LbvHPUUeRmGqh5wK_lHqDEIAcym2iqpbtDad0,1365
59
59
  glaip_sdk/utils/serialization.py,sha256=T1yt_8G2DCFpcxx7XnqFl5slksRXfBCUuLQJTreGYEQ,11806
60
60
  glaip_sdk/utils/validation.py,sha256=QNORcdyvuliEs4EH2_mkDgmoyT9utgl7YNhaf45SEf8,6992
61
- glaip_sdk-0.0.12.dist-info/METADATA,sha256=9nmM8Y3WqIoYIkJytD_HKN2CHtHEG-R1Kdi1_SdsLnY,4984
62
- glaip_sdk-0.0.12.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
63
- glaip_sdk-0.0.12.dist-info/entry_points.txt,sha256=EGs8NO8J1fdFMWA3CsF7sKBEvtHb_fujdCoNPhfMouE,47
64
- glaip_sdk-0.0.12.dist-info/RECORD,,
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,,