data-designer 0.4.0rc2__tar.gz → 0.5.0rc1__tar.gz

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.
Files changed (84) hide show
  1. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/PKG-INFO +3 -3
  2. data_designer-0.5.0rc1/dev-tools/hatch_build.py +31 -0
  3. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/pyproject.toml +23 -14
  4. data_designer-0.5.0rc1/src/data_designer/cli/commands/list.py +255 -0
  5. data_designer-0.5.0rc1/src/data_designer/cli/commands/mcp.py +13 -0
  6. data_designer-0.5.0rc1/src/data_designer/cli/commands/tools.py +13 -0
  7. data_designer-0.5.0rc1/src/data_designer/cli/controllers/mcp_provider_controller.py +241 -0
  8. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/src/data_designer/cli/controllers/provider_controller.py +6 -2
  9. data_designer-0.5.0rc1/src/data_designer/cli/controllers/tool_controller.py +236 -0
  10. data_designer-0.5.0rc1/src/data_designer/cli/forms/mcp_provider_builder.py +219 -0
  11. data_designer-0.5.0rc1/src/data_designer/cli/forms/tool_builder.py +204 -0
  12. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/src/data_designer/cli/main.py +3 -1
  13. data_designer-0.5.0rc1/src/data_designer/cli/repositories/mcp_provider_repository.py +48 -0
  14. data_designer-0.5.0rc1/src/data_designer/cli/repositories/tool_repository.py +44 -0
  15. data_designer-0.5.0rc1/src/data_designer/cli/services/mcp_provider_service.py +86 -0
  16. data_designer-0.5.0rc1/src/data_designer/cli/services/tool_service.py +83 -0
  17. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/src/data_designer/interface/data_designer.py +27 -0
  18. data_designer-0.5.0rc1/tests/cli/commands/test_list_command.py +158 -0
  19. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/tests/cli/conftest.py +91 -0
  20. data_designer-0.5.0rc1/tests/cli/controllers/test_mcp_provider_controller.py +368 -0
  21. data_designer-0.5.0rc1/tests/cli/controllers/test_tool_controller.py +413 -0
  22. data_designer-0.5.0rc1/tests/cli/forms/test_mcp_provider_builder.py +609 -0
  23. data_designer-0.5.0rc1/tests/cli/forms/test_tool_builder.py +628 -0
  24. data_designer-0.5.0rc1/tests/cli/repositories/test_mcp_provider_repository.py +39 -0
  25. data_designer-0.5.0rc1/tests/cli/repositories/test_tool_repository.py +37 -0
  26. data_designer-0.5.0rc1/tests/cli/services/test_mcp_provider_service.py +110 -0
  27. data_designer-0.5.0rc1/tests/cli/services/test_tool_service.py +92 -0
  28. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/tests/conftest.py +4 -1
  29. data_designer-0.4.0rc2/src/data_designer/cli/commands/list.py +0 -118
  30. data_designer-0.4.0rc2/src/data_designer/interface/_version.py +0 -34
  31. data_designer-0.4.0rc2/tests/cli/commands/test_list_command.py +0 -82
  32. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/.gitignore +0 -0
  33. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/README.md +0 -0
  34. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/src/data_designer/cli/README.md +0 -0
  35. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/src/data_designer/cli/__init__.py +0 -0
  36. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/src/data_designer/cli/commands/__init__.py +0 -0
  37. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/src/data_designer/cli/commands/download.py +0 -0
  38. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/src/data_designer/cli/commands/models.py +0 -0
  39. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/src/data_designer/cli/commands/providers.py +0 -0
  40. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/src/data_designer/cli/commands/reset.py +0 -0
  41. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/src/data_designer/cli/controllers/__init__.py +0 -0
  42. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/src/data_designer/cli/controllers/download_controller.py +0 -0
  43. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/src/data_designer/cli/controllers/model_controller.py +0 -0
  44. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/src/data_designer/cli/forms/__init__.py +0 -0
  45. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/src/data_designer/cli/forms/builder.py +0 -0
  46. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/src/data_designer/cli/forms/field.py +0 -0
  47. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/src/data_designer/cli/forms/form.py +0 -0
  48. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/src/data_designer/cli/forms/model_builder.py +0 -0
  49. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/src/data_designer/cli/forms/provider_builder.py +0 -0
  50. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/src/data_designer/cli/repositories/__init__.py +0 -0
  51. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/src/data_designer/cli/repositories/base.py +0 -0
  52. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/src/data_designer/cli/repositories/model_repository.py +0 -0
  53. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/src/data_designer/cli/repositories/persona_repository.py +0 -0
  54. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/src/data_designer/cli/repositories/provider_repository.py +0 -0
  55. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/src/data_designer/cli/services/__init__.py +0 -0
  56. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/src/data_designer/cli/services/download_service.py +0 -0
  57. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/src/data_designer/cli/services/model_service.py +0 -0
  58. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/src/data_designer/cli/services/provider_service.py +0 -0
  59. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/src/data_designer/cli/ui.py +0 -0
  60. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/src/data_designer/cli/utils.py +0 -0
  61. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/src/data_designer/interface/__init__.py +0 -0
  62. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/src/data_designer/interface/errors.py +0 -0
  63. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/src/data_designer/interface/results.py +0 -0
  64. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/tests/cli/commands/test_download_command.py +0 -0
  65. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/tests/cli/commands/test_models_command.py +0 -0
  66. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/tests/cli/commands/test_providers_command.py +0 -0
  67. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/tests/cli/commands/test_reset_command.py +0 -0
  68. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/tests/cli/controllers/test_download_controller.py +0 -0
  69. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/tests/cli/controllers/test_model_controller.py +0 -0
  70. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/tests/cli/controllers/test_provider_controller.py +0 -0
  71. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/tests/cli/forms/test_field.py +0 -0
  72. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/tests/cli/forms/test_form.py +0 -0
  73. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/tests/cli/forms/test_model_builder.py +0 -0
  74. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/tests/cli/forms/test_provider_builder.py +0 -0
  75. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/tests/cli/repositories/test_model_repository.py +0 -0
  76. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/tests/cli/repositories/test_persona_repository.py +0 -0
  77. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/tests/cli/repositories/test_provider_repository.py +0 -0
  78. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/tests/cli/services/test_download_service.py +0 -0
  79. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/tests/cli/services/test_model_service.py +0 -0
  80. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/tests/cli/services/test_provider_service.py +0 -0
  81. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/tests/cli/test_cli_utils.py +0 -0
  82. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/tests/interface/test_data_designer.py +0 -0
  83. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/tests/interface/test_results.py +0 -0
  84. {data_designer-0.4.0rc2 → data_designer-0.5.0rc1}/tests/test_import_perf.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: data-designer
3
- Version: 0.4.0rc2
3
+ Version: 0.5.0rc1
4
4
  Summary: General framework for synthetic data generation
5
5
  License-Expression: Apache-2.0
6
6
  Classifier: Development Status :: 4 - Beta
@@ -14,8 +14,8 @@ Classifier: Programming Language :: Python :: 3.13
14
14
  Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
15
15
  Classifier: Topic :: Software Development
16
16
  Requires-Python: >=3.10
17
- Requires-Dist: data-designer-config
18
- Requires-Dist: data-designer-engine
17
+ Requires-Dist: data-designer-config==0.5.0rc1
18
+ Requires-Dist: data-designer-engine==0.5.0rc1
19
19
  Requires-Dist: prompt-toolkit<4,>=3.0.0
20
20
  Requires-Dist: typer<1,>=0.12.0
21
21
  Description-Content-Type: text/markdown
@@ -0,0 +1,31 @@
1
+ # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """Custom hatch metadata hook to sync README from root.
5
+
6
+ This hook runs during metadata resolution (before build hooks) to ensure
7
+ the README.md from the repository root is copied before hatchling validates
8
+ that the readme file exists.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import shutil
14
+ from pathlib import Path
15
+ from typing import Any
16
+
17
+ from hatchling.metadata.plugin.interface import MetadataHookInterface
18
+
19
+
20
+ class ReadmeSyncHook(MetadataHookInterface):
21
+ """Metadata hook that copies README.md from repository root before building."""
22
+
23
+ PLUGIN_NAME = "readme-sync"
24
+
25
+ def update(self, metadata: dict[str, Any]) -> None:
26
+ """Copy README.md from repository root to package directory."""
27
+ root_readme = Path(self.root) / ".." / ".." / "README.md"
28
+ package_readme = Path(self.root) / "README.md"
29
+
30
+ if root_readme.exists():
31
+ shutil.copy2(root_readme, package_readme)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "data-designer"
3
- dynamic = ["version"]
3
+ dynamic = ["version", "dependencies"]
4
4
  description = "General framework for synthetic data generation"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -19,27 +19,31 @@ classifiers = [
19
19
  "Programming Language :: Python :: 3.13",
20
20
  ]
21
21
 
22
- dependencies = [
23
- "data-designer-config",
24
- "data-designer-engine",
25
- "prompt-toolkit>=3.0.0,<4",
26
- "typer>=0.12.0,<1",
27
- ]
28
-
29
22
  [project.scripts]
30
23
  data-designer = "data_designer.cli:main"
31
24
 
32
25
  [build-system]
33
- requires = ["hatchling", "hatch-vcs"]
26
+ requires = ["hatchling", "uv-dynamic-versioning>=0.7.0"]
34
27
  build-backend = "hatchling.build"
35
28
 
36
29
  [tool.hatch.version]
37
- source = "vcs"
38
- fallback-version = "0.1.0.dev0"
39
- raw-options = { root = "../.." }
30
+ source = "uv-dynamic-versioning"
31
+
32
+ [tool.uv-dynamic-versioning]
33
+ vcs = "git"
34
+ style = "pep440"
35
+ bump = true
40
36
 
41
- [tool.hatch.build.hooks.vcs]
42
- version-file = "src/data_designer/interface/_version.py"
37
+ [tool.hatch.metadata.hooks.custom]
38
+ path = "dev-tools/hatch_build.py"
39
+
40
+ [tool.hatch.metadata.hooks.uv-dynamic-versioning]
41
+ dependencies = [
42
+ "data-designer-config=={{ version }}",
43
+ "data-designer-engine=={{ version }}",
44
+ "prompt-toolkit>=3.0.0,<4",
45
+ "typer>=0.12.0,<1",
46
+ ]
43
47
 
44
48
  [tool.hatch.build.targets.wheel]
45
49
  packages = ["src/data_designer"]
@@ -47,6 +51,11 @@ packages = ["src/data_designer"]
47
51
  [tool.ruff]
48
52
  extend = "../../pyproject.toml"
49
53
 
54
+ [tool.pytest.ini_options]
55
+ testpaths = ["tests"]
56
+ asyncio_default_fixture_loop_scope = "session"
57
+ env = ["DISABLE_DATA_DESIGNER_PLUGINS=true"]
58
+
50
59
  [tool.uv]
51
60
  package = true
52
61
 
@@ -0,0 +1,255 @@
1
+ # SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ from __future__ import annotations
5
+
6
+ import re
7
+
8
+ from rich.table import Table
9
+
10
+ from data_designer.cli.repositories.mcp_provider_repository import MCPProviderRepository
11
+ from data_designer.cli.repositories.model_repository import ModelRepository
12
+ from data_designer.cli.repositories.provider_repository import ProviderRepository
13
+ from data_designer.cli.repositories.tool_repository import ToolRepository
14
+ from data_designer.cli.ui import console, print_error, print_header, print_info, print_warning
15
+ from data_designer.config.mcp import LocalStdioMCPProvider, MCPProvider
16
+ from data_designer.config.utils.constants import DATA_DESIGNER_HOME, NordColor
17
+
18
+ # Pattern for valid environment variable names (uppercase letters, digits, underscores, not starting with digit)
19
+ _ENV_VAR_PATTERN = re.compile(r"^[A-Z_][A-Z0-9_]*$")
20
+
21
+
22
+ def _is_env_var_name(value: str) -> bool:
23
+ """Check if a string looks like an environment variable name.
24
+
25
+ Returns True only if the string matches the pattern for typical env var names:
26
+ - All uppercase letters, digits, and underscores only
27
+ - Does not start with a digit
28
+ - At least one character
29
+
30
+ This is intentionally conservative to avoid leaking secrets that happen
31
+ to be uppercase (e.g., base32-encoded API keys).
32
+ """
33
+ return bool(_ENV_VAR_PATTERN.match(value))
34
+
35
+
36
+ def _mask_api_key(api_key: str | None) -> str:
37
+ """Mask an API key for display, preserving environment variable names.
38
+
39
+ Args:
40
+ api_key: The API key value or None.
41
+
42
+ Returns:
43
+ A display string: "(not set)" if None, the original value if it looks
44
+ like an env var name, or a masked version showing only the last 4 chars.
45
+ """
46
+ if not api_key:
47
+ return "(not set)"
48
+ # Only show unmasked if it looks like a valid environment variable name
49
+ if _is_env_var_name(api_key):
50
+ return api_key
51
+ return "***" + api_key[-4:] if len(api_key) > 4 else "***"
52
+
53
+
54
+ def list_command() -> None:
55
+ """List current Data Designer configurations.
56
+
57
+ Returns:
58
+ None
59
+ """
60
+ # Determine config directory
61
+ print_header("Data Designer Configurations")
62
+ print_info(f"Configuration directory: {DATA_DESIGNER_HOME}")
63
+ console.print()
64
+
65
+ # Display all configuration types
66
+ display_providers(ProviderRepository(DATA_DESIGNER_HOME))
67
+ display_models(ModelRepository(DATA_DESIGNER_HOME))
68
+ display_mcp_providers(MCPProviderRepository(DATA_DESIGNER_HOME))
69
+ display_tool_configs(ToolRepository(DATA_DESIGNER_HOME))
70
+
71
+
72
+ def display_providers(provider_repo: ProviderRepository) -> None:
73
+ """Load and display model providers.
74
+
75
+ Args:
76
+ provider_repo: Provider repository
77
+
78
+ Returns:
79
+ None
80
+ """
81
+ try:
82
+ provider_registry = provider_repo.load()
83
+
84
+ if not provider_registry:
85
+ print_warning("Providers have not been configured. Run 'data-designer config providers' to configure them.")
86
+ console.print()
87
+ return
88
+
89
+ # Display as table
90
+ table = Table(title="Model Providers", border_style=NordColor.NORD8.value)
91
+ table.add_column("Name", style=NordColor.NORD14.value, no_wrap=True)
92
+ table.add_column("Endpoint", style=NordColor.NORD4.value)
93
+ table.add_column("Type", style=NordColor.NORD9.value, no_wrap=True)
94
+ table.add_column("API Key", style=NordColor.NORD7.value)
95
+ table.add_column("Default", style=NordColor.NORD13.value, justify="center")
96
+
97
+ default_name = provider_registry.default or provider_registry.providers[0].name
98
+
99
+ for provider in provider_registry.providers:
100
+ is_default = "✓" if provider.name == default_name else ""
101
+ api_key_display = _mask_api_key(provider.api_key)
102
+
103
+ table.add_row(
104
+ provider.name,
105
+ provider.endpoint,
106
+ provider.provider_type,
107
+ api_key_display,
108
+ is_default,
109
+ )
110
+
111
+ console.print(table)
112
+ console.print()
113
+ except Exception as e:
114
+ print_error(f"Error loading provider configuration: {e}")
115
+ console.print()
116
+
117
+
118
+ def display_models(model_repo: ModelRepository) -> None:
119
+ """Load and display model configurations.
120
+
121
+ Args:
122
+ model_repo: Model repository
123
+
124
+ Returns:
125
+ None
126
+ """
127
+ try:
128
+ registry = model_repo.load()
129
+
130
+ if not registry:
131
+ print_warning("Models have not been configured. Run 'data-designer config models' to configure them.")
132
+ console.print()
133
+ return
134
+
135
+ # Display as table
136
+ table = Table(title="Model Configurations", border_style=NordColor.NORD8.value)
137
+ table.add_column("Alias", style=NordColor.NORD14.value, no_wrap=True)
138
+ table.add_column("Model", style=NordColor.NORD4.value)
139
+ table.add_column("Provider", style=NordColor.NORD9.value, no_wrap=True)
140
+ table.add_column("Inference Parameters", style=NordColor.NORD15.value)
141
+
142
+ for mc in registry.model_configs:
143
+ params_display = mc.inference_parameters.format_for_display()
144
+
145
+ table.add_row(
146
+ mc.alias,
147
+ mc.model,
148
+ mc.provider or "(default)",
149
+ params_display,
150
+ )
151
+
152
+ console.print(table)
153
+ console.print()
154
+ except Exception as e:
155
+ print_error(f"Error loading model configuration: {e}")
156
+ console.print()
157
+
158
+
159
+ def display_mcp_providers(mcp_provider_repo: MCPProviderRepository) -> None:
160
+ """Load and display MCP provider configurations.
161
+
162
+ Handles both MCPProvider (remote SSE) and LocalStdioMCPProvider (subprocess).
163
+
164
+ Args:
165
+ mcp_provider_repo: MCP provider repository
166
+
167
+ Returns:
168
+ None
169
+ """
170
+ try:
171
+ registry = mcp_provider_repo.load()
172
+
173
+ if not registry:
174
+ print_warning("MCP providers have not been configured. Run 'data-designer config mcp' to configure them.")
175
+ console.print()
176
+ return
177
+
178
+ # Display as table
179
+ table = Table(title="MCP Providers", border_style=NordColor.NORD8.value)
180
+ table.add_column("Name", style=NordColor.NORD14.value, no_wrap=True)
181
+ table.add_column("Endpoint / Command", style=NordColor.NORD4.value)
182
+ table.add_column("Type", style=NordColor.NORD9.value, no_wrap=True)
183
+ table.add_column("API Key / Env", style=NordColor.NORD7.value)
184
+
185
+ for provider in registry.providers:
186
+ if isinstance(provider, MCPProvider):
187
+ endpoint_display = provider.endpoint
188
+ api_key_display = _mask_api_key(provider.api_key)
189
+ elif isinstance(provider, LocalStdioMCPProvider):
190
+ # Display command + args for stdio provider
191
+ args_str = " ".join(provider.args) if provider.args else ""
192
+ endpoint_display = f"{provider.command} {args_str}".strip()
193
+ # Show env vars count for stdio provider
194
+ api_key_display = f"{len(provider.env)} env vars" if provider.env else "(none)"
195
+ else:
196
+ endpoint_display = "(unknown)"
197
+ api_key_display = "(unknown)"
198
+
199
+ table.add_row(
200
+ provider.name,
201
+ endpoint_display,
202
+ provider.provider_type,
203
+ api_key_display,
204
+ )
205
+
206
+ console.print(table)
207
+ console.print()
208
+ except Exception as e:
209
+ print_error(f"Error loading MCP provider configuration: {e}")
210
+ console.print()
211
+
212
+
213
+ def display_tool_configs(tool_repo: ToolRepository) -> None:
214
+ """Load and display tool configurations.
215
+
216
+ Args:
217
+ tool_repo: Tool repository
218
+
219
+ Returns:
220
+ None
221
+ """
222
+ try:
223
+ registry = tool_repo.load()
224
+
225
+ if not registry:
226
+ print_warning("Tool configs have not been configured. Run 'data-designer config tools' to configure them.")
227
+ console.print()
228
+ return
229
+
230
+ # Display as table
231
+ table = Table(title="Tool Configurations", border_style=NordColor.NORD8.value)
232
+ table.add_column("Alias", style=NordColor.NORD14.value, no_wrap=True)
233
+ table.add_column("Providers", style=NordColor.NORD4.value)
234
+ table.add_column("Allowed Tools", style=NordColor.NORD9.value)
235
+ table.add_column("Max Turns", style=NordColor.NORD7.value, justify="center")
236
+ table.add_column("Timeout", style=NordColor.NORD15.value, justify="center")
237
+
238
+ for tc in registry.tool_configs:
239
+ providers_display = ", ".join(tc.providers)
240
+ allow_tools_display = ", ".join(tc.allow_tools) if tc.allow_tools else "(all)"
241
+ timeout_display = f"{tc.timeout_sec}s" if tc.timeout_sec else "(none)"
242
+
243
+ table.add_row(
244
+ tc.tool_alias,
245
+ providers_display,
246
+ allow_tools_display,
247
+ str(tc.max_tool_call_turns),
248
+ timeout_display,
249
+ )
250
+
251
+ console.print(table)
252
+ console.print()
253
+ except Exception as e:
254
+ print_error(f"Error loading tool configuration: {e}")
255
+ console.print()
@@ -0,0 +1,13 @@
1
+ # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ from __future__ import annotations
5
+
6
+ from data_designer.cli.controllers.mcp_provider_controller import MCPProviderController
7
+ from data_designer.config.utils.constants import DATA_DESIGNER_HOME
8
+
9
+
10
+ def mcp_command() -> None:
11
+ """Configure MCP providers interactively."""
12
+ controller = MCPProviderController(DATA_DESIGNER_HOME)
13
+ controller.run()
@@ -0,0 +1,13 @@
1
+ # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ from __future__ import annotations
5
+
6
+ from data_designer.cli.controllers.tool_controller import ToolController
7
+ from data_designer.config.utils.constants import DATA_DESIGNER_HOME
8
+
9
+
10
+ def tools_command() -> None:
11
+ """Configure tool configs interactively."""
12
+ controller = ToolController(DATA_DESIGNER_HOME)
13
+ controller.run()
@@ -0,0 +1,241 @@
1
+ # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ from __future__ import annotations
5
+
6
+ import copy
7
+ import re
8
+ from pathlib import Path
9
+
10
+ # Pattern for valid environment variable names (uppercase letters, digits, underscores, not starting with digit)
11
+ _ENV_VAR_PATTERN = re.compile(r"^[A-Z_][A-Z0-9_]*$")
12
+
13
+ from data_designer.cli.forms.mcp_provider_builder import MCPProviderFormBuilder, MCPProviderT
14
+ from data_designer.cli.repositories.mcp_provider_repository import MCPProviderRepository
15
+ from data_designer.cli.services.mcp_provider_service import MCPProviderService
16
+ from data_designer.cli.ui import (
17
+ confirm_action,
18
+ console,
19
+ display_config_preview,
20
+ print_error,
21
+ print_header,
22
+ print_info,
23
+ print_success,
24
+ select_with_arrows,
25
+ )
26
+ from data_designer.config.mcp import LocalStdioMCPProvider, MCPProvider
27
+
28
+
29
+ class MCPProviderController:
30
+ """Controller for MCP provider configuration workflows."""
31
+
32
+ def __init__(self, config_dir: Path):
33
+ self.config_dir = config_dir
34
+ self.repository = MCPProviderRepository(config_dir)
35
+ self.service = MCPProviderService(self.repository)
36
+
37
+ def run(self) -> None:
38
+ """Main entry point for MCP provider configuration."""
39
+ print_header("Configure MCP Providers")
40
+ print_info(f"Configuration directory: {self.config_dir}")
41
+ console.print()
42
+
43
+ # Check for existing configuration
44
+ providers = self.service.list_all()
45
+
46
+ if providers:
47
+ self._show_existing_config()
48
+ mode = self._select_mode()
49
+ else:
50
+ print_info("No MCP providers configured yet")
51
+ console.print()
52
+ mode = "add"
53
+
54
+ if mode is None:
55
+ print_info("No changes made")
56
+ return
57
+
58
+ # Execute selected mode
59
+ mode_handlers = {
60
+ "add": self._handle_add,
61
+ "update": self._handle_update,
62
+ "delete": self._handle_delete,
63
+ "delete_all": self._handle_delete_all,
64
+ }
65
+
66
+ handler = mode_handlers.get(mode)
67
+ if handler:
68
+ handler()
69
+
70
+ def _show_existing_config(self) -> None:
71
+ """Display current configuration."""
72
+ registry = self.repository.load()
73
+ if not registry:
74
+ return
75
+
76
+ print_info(f"Found {len(registry.providers)} configured MCP provider(s)")
77
+ console.print()
78
+
79
+ # Display configuration (with masked API keys)
80
+ config_dict = registry.model_dump(mode="json", exclude_none=True)
81
+ masked_config = self._mask_api_keys(config_dict)
82
+ display_config_preview(masked_config, "Current Configuration")
83
+ console.print()
84
+
85
+ def _mask_api_keys(self, config: dict) -> dict:
86
+ """Mask API keys in configuration for display."""
87
+ masked = copy.deepcopy(config)
88
+
89
+ if "providers" in masked:
90
+ for provider in masked["providers"]:
91
+ if "api_key" in provider and provider["api_key"]:
92
+ api_key = provider["api_key"]
93
+ # Only show unmasked if it looks like a valid environment variable name
94
+ if not _ENV_VAR_PATTERN.match(api_key):
95
+ provider["api_key"] = "***" + api_key[-4:] if len(api_key) > 4 else "***"
96
+
97
+ return masked
98
+
99
+ def _select_mode(self) -> str | None:
100
+ """Prompt user to select operation mode."""
101
+ options = {
102
+ "add": "Add a new MCP provider",
103
+ "update": "Update an existing MCP provider",
104
+ "delete": "Delete an MCP provider",
105
+ "delete_all": "Delete all MCP providers",
106
+ "exit": "Exit without changes",
107
+ }
108
+
109
+ result = select_with_arrows(
110
+ options,
111
+ "What would you like to do?",
112
+ default_key="add",
113
+ allow_back=False,
114
+ )
115
+
116
+ return None if result == "exit" or result is None else result
117
+
118
+ def _handle_add(self) -> None:
119
+ """Handle adding new MCP providers."""
120
+ existing_names = {p.name for p in self.service.list_all()}
121
+
122
+ while True:
123
+ # Create builder with current existing names
124
+ builder = MCPProviderFormBuilder(existing_names)
125
+ provider = builder.run()
126
+
127
+ if provider is None:
128
+ break
129
+
130
+ # Attempt to add
131
+ try:
132
+ self.service.add(provider)
133
+ print_success(f"MCP provider '{provider.name}' added successfully")
134
+ existing_names.add(provider.name)
135
+ except ValueError as e:
136
+ print_error(f"Failed to add MCP provider: {e}")
137
+ break
138
+
139
+ # Ask if they want to add more
140
+ if not self._confirm_add_another():
141
+ break
142
+
143
+ def _handle_update(self) -> None:
144
+ """Handle updating an existing MCP provider."""
145
+ providers = self.service.list_all()
146
+ if not providers:
147
+ print_error("No MCP providers to update")
148
+ return
149
+
150
+ # Select provider to update
151
+ selected_name = self._select_provider(providers, "Select MCP provider to update")
152
+ if selected_name is None:
153
+ return
154
+
155
+ provider = self.service.get_by_name(selected_name)
156
+ if not provider:
157
+ print_error(f"MCP provider '{selected_name}' not found")
158
+ return
159
+
160
+ # Run builder with existing data
161
+ existing_names = {p.name for p in providers if p.name != selected_name}
162
+ builder = MCPProviderFormBuilder(existing_names)
163
+ initial_data = provider.model_dump(mode="json", exclude_none=True)
164
+ updated_provider = builder.run(initial_data)
165
+
166
+ if updated_provider:
167
+ try:
168
+ self.service.update(selected_name, updated_provider)
169
+ print_success(f"MCP provider '{updated_provider.name}' updated successfully")
170
+ except ValueError as e:
171
+ print_error(f"Failed to update MCP provider: {e}")
172
+
173
+ def _handle_delete(self) -> None:
174
+ """Handle deleting an MCP provider."""
175
+ providers = self.service.list_all()
176
+ if not providers:
177
+ print_error("No MCP providers to delete")
178
+ return
179
+
180
+ # Select provider to delete
181
+ selected_name = self._select_provider(providers, "Select MCP provider to delete")
182
+ if selected_name is None:
183
+ return
184
+
185
+ # Confirm deletion
186
+ console.print()
187
+ if confirm_action(f"Delete MCP provider '{selected_name}'?", default=False):
188
+ try:
189
+ self.service.delete(selected_name)
190
+ print_success(f"MCP provider '{selected_name}' deleted successfully")
191
+ except ValueError as e:
192
+ print_error(f"Failed to delete MCP provider: {e}")
193
+
194
+ def _handle_delete_all(self) -> None:
195
+ """Handle deleting all MCP providers."""
196
+ providers = self.service.list_all()
197
+ if not providers:
198
+ print_error("No MCP providers to delete")
199
+ return
200
+
201
+ # List providers to be deleted
202
+ console.print()
203
+ provider_count = len(providers)
204
+ provider_names = ", ".join([f"'{p.name}'" for p in providers])
205
+
206
+ if confirm_action(
207
+ f"Delete ALL ({provider_count}) MCP provider(s): {provider_names}?\n This action cannot be undone.",
208
+ default=False,
209
+ ):
210
+ try:
211
+ self.repository.delete()
212
+ print_success(f"All ({provider_count}) MCP provider(s) deleted successfully")
213
+ except Exception as e:
214
+ print_error(f"Failed to delete all MCP providers: {e}")
215
+
216
+ def _select_provider(self, providers: list[MCPProviderT], prompt: str, default: str | None = None) -> str | None:
217
+ """Helper to select an MCP provider from list."""
218
+ options = {}
219
+ for p in providers:
220
+ if isinstance(p, MCPProvider):
221
+ options[p.name] = f"{p.name} (SSE: {p.endpoint})"
222
+ elif isinstance(p, LocalStdioMCPProvider):
223
+ options[p.name] = f"{p.name} (stdio: {p.command})"
224
+ else:
225
+ options[p.name] = p.name
226
+ return select_with_arrows(
227
+ options,
228
+ prompt,
229
+ default_key=default or providers[0].name,
230
+ allow_back=False,
231
+ )
232
+
233
+ def _confirm_add_another(self) -> bool:
234
+ """Ask if user wants to add another MCP provider."""
235
+ result = select_with_arrows(
236
+ {"yes": "Add another MCP provider", "no": "Finish"},
237
+ "Add another MCP provider?",
238
+ default_key="no",
239
+ allow_back=False,
240
+ )
241
+ return result == "yes"
@@ -4,10 +4,14 @@
4
4
  from __future__ import annotations
5
5
 
6
6
  import copy
7
+ import re
7
8
  from pathlib import Path
8
9
  from typing import TYPE_CHECKING
9
10
 
10
11
  from data_designer.cli.forms.provider_builder import ProviderFormBuilder
12
+
13
+ # Pattern for valid environment variable names (uppercase letters, digits, underscores, not starting with digit)
14
+ _ENV_VAR_PATTERN = re.compile(r"^[A-Z_][A-Z0-9_]*$")
11
15
  from data_designer.cli.repositories.model_repository import ModelRepository
12
16
  from data_designer.cli.repositories.provider_repository import ProviderRepository
13
17
  from data_designer.cli.services.model_service import ModelService
@@ -95,8 +99,8 @@ class ProviderController:
95
99
  for provider in masked["providers"]:
96
100
  if "api_key" in provider and provider["api_key"]:
97
101
  api_key = provider["api_key"]
98
- # Keep environment variable names visible
99
- if not api_key.isupper():
102
+ # Only show unmasked if it looks like a valid environment variable name
103
+ if not _ENV_VAR_PATTERN.match(api_key):
100
104
  provider["api_key"] = "***" + api_key[-4:] if len(api_key) > 4 else "***"
101
105
 
102
106
  return masked