datarobot-genai 0.1.63__tar.gz → 0.1.65__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 (101) hide show
  1. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/PKG-INFO +1 -1
  2. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/pyproject.toml +1 -1
  3. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/core/dr_mcp_server.py +10 -3
  4. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/core/dr_mcp_server_logo.py +12 -1
  5. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/core/dynamic_prompts/dr_lib.py +17 -7
  6. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/core/dynamic_prompts/register.py +36 -4
  7. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/.gitignore +0 -0
  8. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/AUTHORS +0 -0
  9. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/LICENSE +0 -0
  10. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/README.md +0 -0
  11. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/__init__.py +0 -0
  12. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/core/__init__.py +0 -0
  13. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/core/agents/__init__.py +0 -0
  14. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/core/agents/base.py +0 -0
  15. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/core/chat/__init__.py +0 -0
  16. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/core/chat/auth.py +0 -0
  17. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/core/chat/client.py +0 -0
  18. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/core/chat/responses.py +0 -0
  19. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/core/cli/__init__.py +0 -0
  20. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/core/cli/agent_environment.py +0 -0
  21. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/core/cli/agent_kernel.py +0 -0
  22. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/core/custom_model.py +0 -0
  23. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/core/mcp/__init__.py +0 -0
  24. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/core/mcp/common.py +0 -0
  25. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/core/telemetry_agent.py +0 -0
  26. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/core/utils/__init__.py +0 -0
  27. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/core/utils/auth.py +0 -0
  28. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/core/utils/urls.py +0 -0
  29. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/crewai/__init__.py +0 -0
  30. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/crewai/agent.py +0 -0
  31. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/crewai/base.py +0 -0
  32. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/crewai/events.py +0 -0
  33. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/crewai/mcp.py +0 -0
  34. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/__init__.py +0 -0
  35. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/core/__init__.py +0 -0
  36. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/core/auth.py +0 -0
  37. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/core/clients.py +0 -0
  38. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/core/config.py +0 -0
  39. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/core/config_utils.py +0 -0
  40. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/core/constants.py +0 -0
  41. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/core/credentials.py +0 -0
  42. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/core/dynamic_prompts/__init__.py +0 -0
  43. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/core/dynamic_prompts/controllers.py +0 -0
  44. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/core/dynamic_prompts/utils.py +0 -0
  45. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/core/dynamic_tools/__init__.py +0 -0
  46. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/__init__.py +0 -0
  47. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/adapters/__init__.py +0 -0
  48. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/adapters/base.py +0 -0
  49. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/adapters/default.py +0 -0
  50. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/adapters/drum.py +0 -0
  51. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/config.py +0 -0
  52. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/controllers.py +0 -0
  53. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/metadata.py +0 -0
  54. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/register.py +0 -0
  55. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/schemas/drum_agentic_fallback_schema.json +0 -0
  56. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/schemas/drum_prediction_fallback_schema.json +0 -0
  57. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/core/dynamic_tools/register.py +0 -0
  58. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/core/dynamic_tools/schema.py +0 -0
  59. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/core/exceptions.py +0 -0
  60. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/core/logging.py +0 -0
  61. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/core/mcp_instance.py +0 -0
  62. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/core/mcp_server_tools.py +0 -0
  63. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/core/memory_management/__init__.py +0 -0
  64. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/core/memory_management/manager.py +0 -0
  65. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/core/memory_management/memory_tools.py +0 -0
  66. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/core/routes.py +0 -0
  67. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/core/routes_utils.py +0 -0
  68. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/core/server_life_cycle.py +0 -0
  69. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/core/telemetry.py +0 -0
  70. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/core/tool_filter.py +0 -0
  71. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/core/utils.py +0 -0
  72. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/server.py +0 -0
  73. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/test_utils/__init__.py +0 -0
  74. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/test_utils/integration_mcp_server.py +0 -0
  75. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/test_utils/mcp_utils_ete.py +0 -0
  76. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/test_utils/mcp_utils_integration.py +0 -0
  77. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/test_utils/openai_llm_mcp_client.py +0 -0
  78. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/test_utils/tool_base_ete.py +0 -0
  79. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/test_utils/utils.py +0 -0
  80. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/tools/__init__.py +0 -0
  81. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/tools/predictive/__init__.py +0 -0
  82. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/tools/predictive/data.py +0 -0
  83. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/tools/predictive/deployment.py +0 -0
  84. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/tools/predictive/deployment_info.py +0 -0
  85. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/tools/predictive/model.py +0 -0
  86. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/tools/predictive/predict.py +0 -0
  87. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/tools/predictive/predict_realtime.py +0 -0
  88. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/tools/predictive/project.py +0 -0
  89. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/drmcp/tools/predictive/training.py +0 -0
  90. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/langgraph/__init__.py +0 -0
  91. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/langgraph/agent.py +0 -0
  92. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/langgraph/mcp.py +0 -0
  93. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/llama_index/__init__.py +0 -0
  94. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/llama_index/agent.py +0 -0
  95. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/llama_index/base.py +0 -0
  96. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/llama_index/mcp.py +0 -0
  97. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/nat/__init__.py +0 -0
  98. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/nat/agent.py +0 -0
  99. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/nat/datarobot_llm_clients.py +0 -0
  100. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/nat/datarobot_llm_providers.py +0 -0
  101. {datarobot_genai-0.1.63 → datarobot_genai-0.1.65}/src/datarobot_genai/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datarobot-genai
3
- Version: 0.1.63
3
+ Version: 0.1.65
4
4
  Summary: Generic helpers for GenAI
5
5
  Project-URL: Homepage, https://github.com/datarobot-oss/datarobot-genai
6
6
  Author: DataRobot, Inc.
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "datarobot-genai"
7
- version = "0.1.63"
7
+ version = "0.1.65"
8
8
  description = "Generic helpers for GenAI"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10, <3.13"
@@ -184,13 +184,17 @@ class DataRobotMCPServer:
184
184
  prompts = asyncio.run(self._mcp._list_prompts_mcp())
185
185
  resources = asyncio.run(self._mcp._list_resources_mcp())
186
186
 
187
- self._logger.info(f"Registered tools: {len(tools)}")
187
+ tools_count = len(tools)
188
+ prompts_count = len(prompts)
189
+ resources_count = len(resources)
190
+
191
+ self._logger.info(f"Registered tools: {tools_count}")
188
192
  for tool in tools:
189
193
  self._logger.info(f" > {tool.name}")
190
- self._logger.info(f"Registered prompts: {len(prompts)}")
194
+ self._logger.info(f"Registered prompts: {prompts_count}")
191
195
  for prompt in prompts:
192
196
  self._logger.info(f" > {prompt.name}")
193
- self._logger.info(f"Registered resources: {len(resources)}")
197
+ self._logger.info(f"Registered resources: {resources_count}")
194
198
  for resource in resources:
195
199
  self._logger.info(f" > {resource.name}")
196
200
 
@@ -209,6 +213,9 @@ class DataRobotMCPServer:
209
213
  self._mcp,
210
214
  self._mcp_transport,
211
215
  port=self._config.mcp_server_port,
216
+ tools_count=tools_count,
217
+ prompts_count=prompts_count,
218
+ resources_count=resources_count,
212
219
  )
213
220
 
214
221
  if self._mcp_transport == "stdio":
@@ -54,6 +54,9 @@ def log_server_custom_banner(
54
54
  host: str | None = None,
55
55
  port: int | None = None,
56
56
  path: str | None = None,
57
+ tools_count: int | None = None,
58
+ prompts_count: int | None = None,
59
+ resources_count: int | None = None,
57
60
  ) -> None:
58
61
  """
59
62
  Create and log a formatted banner with server information and logo.
@@ -64,13 +67,20 @@ def log_server_custom_banner(
64
67
  host: Host address (for HTTP transports)
65
68
  port: Port number (for HTTP transports)
66
69
  path: Server path (for HTTP transports)
70
+ tools_count: Number of tools registered
71
+ prompts_count: Number of prompts registered
72
+ resources_count: Number of resources registered
67
73
  """
68
74
  # Create the logo text
69
75
  # Use Text with no_wrap and markup disabled to preserve ANSI escape codes
70
76
  logo_text = Text.from_ansi(DR_LOGO_ASCII, no_wrap=True)
71
77
 
72
78
  # Create the main title
73
- title_text = Text(f"DataRobot MCP Server {datarobot_genai_version}", style="bold green")
79
+ title_text = Text(f"DataRobot MCP Server {datarobot_genai_version}", style="dim green")
80
+ stats_text = Text(
81
+ f"{tools_count} tools, {prompts_count} prompts, {resources_count} resources",
82
+ style="bold green",
83
+ )
74
84
 
75
85
  # Create the information table
76
86
  info_table = Table.grid(padding=(0, 1))
@@ -107,6 +117,7 @@ def log_server_custom_banner(
107
117
  Align.center(logo_text),
108
118
  "",
109
119
  Align.center(title_text),
120
+ Align.center(stats_text),
110
121
  "",
111
122
  "",
112
123
  Align.center(info_table),
@@ -11,7 +11,7 @@
11
11
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
-
14
+ from collections import defaultdict
15
15
  from dataclasses import dataclass
16
16
 
17
17
  import datarobot as dr
@@ -34,6 +34,7 @@ class DrVariable:
34
34
  @dataclass
35
35
  class DrPromptVersion:
36
36
  id: str
37
+ prompt_template_id: str
37
38
  version: int
38
39
  prompt_text: str
39
40
  variables: list[DrVariable]
@@ -45,6 +46,7 @@ class DrPromptVersion:
45
46
  ]
46
47
  return cls(
47
48
  id=d["id"],
49
+ prompt_template_id=d["promptTemplateId"],
48
50
  version=d["version"],
49
51
  prompt_text=d["promptText"],
50
52
  variables=variables,
@@ -58,7 +60,9 @@ class DrPrompt:
58
60
  description: str
59
61
 
60
62
  def get_latest_version(self) -> DrPromptVersion | None:
61
- prompt_template_versions = get_datarobot_prompt_template_versions(self.id)
63
+ all_prompt_template_versions = get_datarobot_prompt_template_versions([self.id])
64
+ prompt_template_versions = all_prompt_template_versions.get(self.id)
65
+
62
66
  if not prompt_template_versions:
63
67
  return None
64
68
  latest_version = max(prompt_template_versions, key=lambda v: v.version)
@@ -77,15 +81,21 @@ def get_datarobot_prompt_templates() -> list[DrPrompt]:
77
81
  return [DrPrompt.from_dict(prompt_template) for prompt_template in prompt_templates_data]
78
82
 
79
83
 
80
- def get_datarobot_prompt_template_versions(prompt_template_id: str) -> list[DrPromptVersion]:
84
+ def get_datarobot_prompt_template_versions(
85
+ prompt_template_ids: list[str],
86
+ ) -> dict[str, list[DrPromptVersion]]:
81
87
  prompt_template_versions_data = dr.utils.pagination.unpaginate(
82
- initial_url=f"genai/promptTemplates/{prompt_template_id}/versions/",
83
- initial_params={},
88
+ initial_url="genai/promptTemplates/versions/",
89
+ initial_params={
90
+ "promptTemplateIds": prompt_template_ids,
91
+ },
84
92
  client=get_api_client(),
85
93
  )
86
- prompt_template_versions = []
94
+ prompt_template_versions = defaultdict(list)
87
95
  for prompt_template_version in prompt_template_versions_data:
88
- prompt_template_versions.append(DrPromptVersion.from_dict(prompt_template_version))
96
+ prompt_template_versions[prompt_template_version["promptTemplateId"]].append(
97
+ DrPromptVersion.from_dict(prompt_template_version)
98
+ )
89
99
  return prompt_template_versions
90
100
 
91
101
 
@@ -27,6 +27,7 @@ from datarobot_genai.drmcp.core.mcp_instance import register_prompt
27
27
  from .dr_lib import DrPrompt
28
28
  from .dr_lib import DrPromptVersion
29
29
  from .dr_lib import DrVariable
30
+ from .dr_lib import get_datarobot_prompt_template_versions
30
31
  from .dr_lib import get_datarobot_prompt_templates
31
32
 
32
33
  logger = logging.getLogger(__name__)
@@ -36,11 +37,21 @@ async def register_prompts_from_datarobot_prompt_management() -> None:
36
37
  """Register prompts from DataRobot Prompt Management."""
37
38
  prompts = get_datarobot_prompt_templates()
38
39
  logger.info(f"Found {len(prompts)} prompts in Prompts Management.")
40
+ all_prompts_versions = get_datarobot_prompt_template_versions(
41
+ prompt_template_ids=list({prompt.id for prompt in prompts})
42
+ )
39
43
 
40
44
  # Try to register each prompt, continue on failure
41
45
  for prompt in prompts:
46
+ prompt_versions = all_prompts_versions.get(prompt.id)
47
+ if not prompt_versions:
48
+ logger.warning(f"Prompt template id {prompt.id} has no versions.")
49
+ continue
50
+
51
+ latest_version = max(prompt_versions, key=lambda v: v.version)
52
+
42
53
  try:
43
- await register_prompt_from_datarobot_prompt_management(prompt)
54
+ await register_prompt_from_datarobot_prompt_management(prompt, latest_version)
44
55
  except DynamicPromptRegistrationError:
45
56
  pass
46
57
 
@@ -114,15 +125,36 @@ async def register_prompt_from_datarobot_prompt_management(
114
125
  ) from exc
115
126
 
116
127
 
128
+ def _escape_non_ascii(s: str) -> str:
129
+ out = []
130
+ for ch in s:
131
+ # If its space -> change to underscore
132
+ if ch.isspace():
133
+ out.append("_")
134
+ # ASCII letter, digit or underscore -> keep
135
+ elif ch.isascii() and (ch.isalnum() or ch == "_"):
136
+ out.append(ch)
137
+ # Everything else -> encode as 'xHEX'
138
+ else:
139
+ out.append(f"x{ord(ch):x}")
140
+ return "".join(out)
141
+
142
+
117
143
  def to_valid_mcp_prompt_name(s: str) -> str:
118
144
  """Convert an arbitrary string into a valid MCP prompt name."""
119
- # Replace any sequence of invalid characters with '_'
120
- s = re.sub(r"[^0-9a-zA-Z_]+", "_", s)
121
-
122
145
  # If its ONLY numbers return "prompt_[number]"
123
146
  if s.isdigit():
124
147
  return f"prompt_{s}"
125
148
 
149
+ # First, ASCII-transliterate using hex escape for non-ASCII
150
+ if not s.isascii():
151
+ # whole string non-ascii? -> escape and prefix with prompt_
152
+ encoded = _escape_non_ascii(s)
153
+ return f"prompt_{encoded}"
154
+
155
+ # Replace any sequence of invalid characters with '_'
156
+ s = re.sub(r"[^0-9a-zA-Z_]+", "_", s)
157
+
126
158
  # Remove leading characters that are not letters or underscores (can't start with a digit or _)
127
159
  s = re.sub(r"^[^a-zA-Z]+", "", s)
128
160