datarobot-genai 0.2.24__py3-none-any.whl → 0.2.26__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.
@@ -31,9 +31,6 @@ from .dynamic_prompts.register import register_prompts_from_datarobot_prompt_man
31
31
  from .dynamic_tools.deployment.register import register_tools_of_datarobot_deployments
32
32
  from .logging import MCPLogging
33
33
  from .mcp_instance import mcp
34
- from .mcp_server_tools import get_all_available_tags # noqa # pylint: disable=unused-import
35
- from .mcp_server_tools import get_tool_info_by_name # noqa # pylint: disable=unused-import
36
- from .mcp_server_tools import list_tools_by_tags # noqa # pylint: disable=unused-import
37
34
  from .memory_management.manager import MemoryManager
38
35
  from .routes import register_routes
39
36
  from .routes_utils import prefix_mount_path
@@ -16,20 +16,18 @@ import logging
16
16
  from collections.abc import Callable
17
17
  from functools import wraps
18
18
  from typing import Any
19
- from typing import overload
19
+ from typing import TypedDict
20
20
 
21
21
  from fastmcp import Context
22
22
  from fastmcp import FastMCP
23
23
  from fastmcp.exceptions import NotFoundError
24
24
  from fastmcp.prompts.prompt import Prompt
25
25
  from fastmcp.server.dependencies import get_context
26
- from fastmcp.tools import FunctionTool
27
26
  from fastmcp.tools import Tool
28
- from fastmcp.utilities.types import NotSet
29
- from fastmcp.utilities.types import NotSetT
30
27
  from mcp.types import AnyFunction
31
28
  from mcp.types import Tool as MCPTool
32
29
  from mcp.types import ToolAnnotations
30
+ from typing_extensions import Unpack
33
31
 
34
32
  from .config import MCPServerConfig
35
33
  from .config import get_config
@@ -120,86 +118,6 @@ class TaggedFastMCP(FastMCP):
120
118
  "In stateless mode, clients will see changes on next request."
121
119
  )
122
120
 
123
- @overload
124
- def tool(
125
- self,
126
- name_or_fn: AnyFunction,
127
- *,
128
- name: str | None = None,
129
- title: str | None = None,
130
- description: str | None = None,
131
- tags: set[str] | None = None,
132
- output_schema: dict[str, Any] | None | NotSetT = NotSet,
133
- annotations: ToolAnnotations | dict[str, Any] | None = None,
134
- exclude_args: list[str] | None = None,
135
- meta: dict[str, Any] | None = None,
136
- enabled: bool | None = None,
137
- ) -> FunctionTool: ...
138
-
139
- @overload
140
- def tool(
141
- self,
142
- name_or_fn: str | None = None,
143
- *,
144
- name: str | None = None,
145
- title: str | None = None,
146
- description: str | None = None,
147
- tags: set[str] | None = None,
148
- output_schema: dict[str, Any] | None | NotSetT = NotSet,
149
- annotations: ToolAnnotations | dict[str, Any] | None = None,
150
- exclude_args: list[str] | None = None,
151
- meta: dict[str, Any] | None = None,
152
- enabled: bool | None = None,
153
- ) -> Callable[[AnyFunction], FunctionTool]: ...
154
-
155
- def tool(
156
- self,
157
- name_or_fn: str | Callable[..., Any] | None = None,
158
- *,
159
- name: str | None = None,
160
- title: str | None = None,
161
- description: str | None = None,
162
- tags: set[str] | None = None,
163
- output_schema: dict[str, Any] | None | NotSetT = NotSet,
164
- annotations: ToolAnnotations | dict[str, Any] | None = None,
165
- exclude_args: list[str] | None = None,
166
- meta: dict[str, Any] | None = None,
167
- enabled: bool | None = None,
168
- **kwargs: Any,
169
- ) -> Callable[[AnyFunction], FunctionTool] | FunctionTool:
170
- """
171
- Extend tool decorator that supports tags and other annotations, while remaining
172
- signature-compatible with FastMCP.tool to avoid recursion issues with partials.
173
- """
174
- if isinstance(annotations, dict):
175
- annotations = ToolAnnotations(**annotations)
176
-
177
- # Ensure tags are available both via native fastmcp `tags` and inside annotations
178
- if tags is not None:
179
- tags_ = sorted(tags)
180
- if annotations is None:
181
- annotations = ToolAnnotations() # type: ignore[call-arg]
182
- annotations.tags = tags_ # type: ignore[attr-defined, union-attr]
183
- else:
184
- # At this point, annotations is ToolAnnotations (not dict)
185
- assert isinstance(annotations, ToolAnnotations)
186
- annotations.tags = tags_ # type: ignore[attr-defined]
187
-
188
- return super().tool(
189
- name_or_fn,
190
- name=name,
191
- title=title,
192
- description=description,
193
- tags=tags,
194
- output_schema=output_schema
195
- if output_schema is not None
196
- else kwargs.get("output_schema"),
197
- annotations=annotations,
198
- exclude_args=exclude_args,
199
- meta=meta,
200
- enabled=enabled,
201
- )
202
-
203
121
  async def list_tools(
204
122
  self, tags: list[str] | None = None, match_all: bool = False
205
123
  ) -> list[MCPTool]:
@@ -371,16 +289,37 @@ mcp = TaggedFastMCP(
371
289
  )
372
290
 
373
291
 
292
+ class ToolKwargs(TypedDict, total=False):
293
+ """Keyword arguments passed through to FastMCP's mcp.tool() decorator.
294
+
295
+ All parameters are optional and forwarded directly to FastMCP tool registration.
296
+ See FastMCP documentation for full details on each parameter.
297
+ """
298
+
299
+ name: str | None
300
+ title: str | None
301
+ description: str | None
302
+ icons: list[Any] | None
303
+ tags: set[str] | None
304
+ output_schema: dict[str, Any] | None
305
+ annotations: Any | None
306
+ exclude_args: list[str] | None
307
+ meta: dict[str, Any] | None
308
+ enabled: bool | None
309
+
310
+
374
311
  def dr_core_mcp_tool(
375
- name: str | None = None,
376
- description: str | None = None,
377
- tags: set[str] | None = None,
312
+ **kwargs: Unpack[ToolKwargs],
378
313
  ) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
379
- """Combine decorator that includes mcp.tool() and dr_mcp_extras()."""
314
+ """Combine decorator that includes mcp.tool() and dr_mcp_extras().
315
+
316
+ All keyword arguments are passed through to FastMCP's mcp.tool() decorator.
317
+ See ToolKwargs for available parameters.
318
+ """
380
319
 
381
320
  def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
382
321
  instrumented = dr_mcp_extras()(func)
383
- mcp.tool(name=name, description=description, tags=tags)(instrumented)
322
+ mcp.tool(**kwargs)(instrumented)
384
323
  return instrumented
385
324
 
386
325
  return decorator
@@ -413,27 +352,23 @@ async def memory_aware_wrapper(func: Callable[..., Any], *args: Any, **kwargs: A
413
352
 
414
353
 
415
354
  def dr_mcp_tool(
416
- name: str | None = None,
417
- description: str | None = None,
418
- tags: set[str] | None = None,
355
+ **kwargs: Unpack[ToolKwargs],
419
356
  ) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
420
357
  """Combine decorator that includes mcp.tool(), dr_mcp_extras(), and capture memory ids from
421
358
  the request headers if they exist.
422
359
 
423
- Args:
424
- name: Tool name
425
- description: Tool description
426
- tags: Optional set of tags to apply to the tool
360
+ All keyword arguments are passed through to FastMCP's mcp.tool() decorator.
361
+ See ToolKwargs for available parameters.
427
362
  """
428
363
 
429
364
  def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
430
365
  @wraps(func)
431
- async def wrapper(*args: Any, **kwargs: Any) -> Any:
432
- return await memory_aware_wrapper(func, *args, **kwargs)
366
+ async def wrapper(*args: Any, **inner_kwargs: Any) -> Any:
367
+ return await memory_aware_wrapper(func, *args, **inner_kwargs)
433
368
 
434
369
  # Apply the MCP decorators
435
370
  instrumented = dr_mcp_extras()(wrapper)
436
- mcp.tool(name=name, description=description, tags=tags)(instrumented)
371
+ mcp.tool(**kwargs)(instrumented)
437
372
  return instrumented
438
373
 
439
374
  return decorator
@@ -488,11 +423,10 @@ async def register_tools(
488
423
  # Apply dr_mcp_extras to the memory-aware function
489
424
  wrapped_fn = dr_mcp_extras()(memory_aware_fn)
490
425
 
491
- # Create annotations with tags, deployment_id if provided
492
- annotations = ToolAnnotations() # type: ignore[call-arg]
493
- if tags is not None:
494
- annotations.tags = tags # type: ignore[attr-defined]
426
+ # Create annotations only when additional metadata is required
427
+ annotations: ToolAnnotations | None = None # type: ignore[assignment]
495
428
  if deployment_id is not None:
429
+ annotations = ToolAnnotations() # type: ignore[call-arg]
496
430
  annotations.deployment_id = deployment_id # type: ignore[attr-defined]
497
431
 
498
432
  tool = Tool.from_function(
@@ -41,7 +41,7 @@ def filter_tools_by_tags(
41
41
  filtered_tools = []
42
42
 
43
43
  for tool in tools:
44
- tool_tags = getattr(tool.annotations, "tags", []) if tool.annotations else []
44
+ tool_tags = get_tool_tags(tool)
45
45
 
46
46
  if not tool_tags:
47
47
  continue
@@ -68,9 +68,18 @@ def get_tool_tags(tool: Tool | MCPTool) -> list[str]:
68
68
  -------
69
69
  List of tags for the tool
70
70
  """
71
+ # Primary: native FastMCP meta location
72
+ if hasattr(tool, "meta") and getattr(tool, "meta"):
73
+ fastmcp_meta = tool.meta.get("_fastmcp", {})
74
+ meta_tags = fastmcp_meta.get("tags", [])
75
+ if isinstance(meta_tags, list):
76
+ return meta_tags
77
+
78
+ # Fallback: annotations.tags (for compatibility during transition)
71
79
  if tool.annotations and hasattr(tool.annotations, "tags"):
72
80
  tags = getattr(tool.annotations, "tags", [])
73
81
  return tags if isinstance(tags, list) else []
82
+
74
83
  return []
75
84
 
76
85
 
@@ -14,6 +14,10 @@
14
14
 
15
15
  import json
16
16
  import logging
17
+ from typing import Annotated
18
+
19
+ from fastmcp.exceptions import ToolError
20
+ from fastmcp.tools.tool import ToolResult
17
21
 
18
22
  from datarobot_genai.drmcp.core.clients import get_sdk_client
19
23
  from datarobot_genai.drmcp.core.mcp_instance import dr_mcp_tool
@@ -21,35 +25,39 @@ from datarobot_genai.drmcp.core.mcp_instance import dr_mcp_tool
21
25
  logger = logging.getLogger(__name__)
22
26
 
23
27
 
24
- @dr_mcp_tool(tags={"project", "management", "list"})
25
- async def list_projects() -> str:
26
- """
27
- List all DataRobot projects for the authenticated user.
28
-
29
- Returns
30
- -------
31
- A string summary of the user's DataRobot projects.
32
- """
28
+ @dr_mcp_tool(tags={"predictive", "project", "read", "management", "list"})
29
+ async def list_projects() -> ToolResult:
30
+ """List all DataRobot projects for the authenticated user."""
33
31
  client = get_sdk_client()
34
32
  projects = client.Project.list()
35
- if not projects:
36
- return "No projects found."
37
- return "\n".join(f"{p.id}: {p.project_name}" for p in projects)
33
+ projects = {p.id: p.project_name for p in projects}
38
34
 
35
+ return ToolResult(
36
+ content=(
37
+ json.dumps(projects, indent=2)
38
+ if projects
39
+ else json.dumps({"message": "No projects found."}, indent=2)
40
+ ),
41
+ structured_content=projects,
42
+ )
39
43
 
40
- @dr_mcp_tool(tags={"project", "data", "info"})
41
- async def get_project_dataset_by_name(project_id: str, dataset_name: str) -> str:
42
- """
43
- Get a dataset ID by name for a given project.
44
44
 
45
- Args:
46
- project_id: The ID of the DataRobot project.
47
- dataset_name: The name of the dataset to find (e.g., 'training', 'holdout').
45
+ @dr_mcp_tool(tags={"predictive", "project", "read", "data", "info"})
46
+ async def get_project_dataset_by_name(
47
+ *,
48
+ project_id: Annotated[str, "The ID of the DataRobot project."] | None = None,
49
+ dataset_name: Annotated[str, "The name of the dataset to find (e.g., 'training', 'holdout')."]
50
+ | None = None,
51
+ ) -> ToolError | ToolResult:
52
+ """Get a dataset ID by name for a given project.
48
53
 
49
- Returns
50
- -------
51
- The dataset ID and the dataset type (source or prediction) as a string, or an error message.
54
+ The dataset ID and the dataset type (source or prediction) as a string, or an error message.
52
55
  """
56
+ if not project_id:
57
+ return ToolError("Project ID is required.")
58
+ if not dataset_name:
59
+ return ToolError("Dataset name is required.")
60
+
53
61
  client = get_sdk_client()
54
62
  project = client.Project.get(project_id)
55
63
  all_datasets = []
@@ -61,12 +69,22 @@ async def get_project_dataset_by_name(project_id: str, dataset_name: str) -> str
61
69
  all_datasets.extend([{"type": "prediction", "dataset": ds} for ds in prediction_datasets])
62
70
  for ds in all_datasets:
63
71
  if dataset_name.lower() in ds["dataset"].name.lower():
64
- return json.dumps(
65
- {
72
+ return ToolResult(
73
+ content=(
74
+ json.dumps(
75
+ {
76
+ "dataset_id": ds["dataset"].id,
77
+ "dataset_type": ds["type"],
78
+ },
79
+ indent=2,
80
+ )
81
+ ),
82
+ structured_content={
66
83
  "dataset_id": ds["dataset"].id,
67
84
  "dataset_type": ds["type"],
68
- "ui_panel": ["dataset"],
69
85
  },
70
- indent=2,
71
86
  )
72
- return f"Dataset with name containing '{dataset_name}' not found in project {project_id}."
87
+ return ToolResult(
88
+ content=f"Dataset with name containing '{dataset_name}' not found in project {project_id}.",
89
+ structured_content={},
90
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datarobot-genai
3
- Version: 0.2.24
3
+ Version: 0.2.26
4
4
  Summary: Generic helpers for GenAI
5
5
  Project-URL: Homepage, https://github.com/datarobot-oss/datarobot-genai
6
6
  Author: DataRobot, Inc.
@@ -31,18 +31,17 @@ datarobot_genai/drmcp/core/config.py,sha256=69QDsVVSvjzv1uIHOjtQGzdg7_Ic4sA3vLA6
31
31
  datarobot_genai/drmcp/core/config_utils.py,sha256=U-aieWw7MyP03cGDFIp97JH99ZUfr3vD9uuTzBzxn7w,6428
32
32
  datarobot_genai/drmcp/core/constants.py,sha256=lUwoW_PTrbaBGqRJifKqCn3EoFacoEgdO-CpoFVrUoU,739
33
33
  datarobot_genai/drmcp/core/credentials.py,sha256=PYEUDNMVw1BoMzZKLkPVTypNkVevEPtmk3scKnE-zYg,6706
34
- datarobot_genai/drmcp/core/dr_mcp_server.py,sha256=yKaIe7Qq23Zgny7Q1dc48iEcpfM8z2Ne6iouGYL58AE,14392
34
+ datarobot_genai/drmcp/core/dr_mcp_server.py,sha256=czcjbwhZAeW9EtG_Bys0GARPOuQulstkiU7FG48Q9bg,14118
35
35
  datarobot_genai/drmcp/core/dr_mcp_server_logo.py,sha256=hib-nfR1SNTW6CnpFsFCkL9H_OMwa4YYyinV7VNOuLk,4708
36
36
  datarobot_genai/drmcp/core/exceptions.py,sha256=eqsGI-lxybgvWL5w4BFhbm3XzH1eU5tetwjnhJxelpc,905
37
37
  datarobot_genai/drmcp/core/logging.py,sha256=Y_hig4eBWiXGaVV7B_3wBcaYVRNH4ydptbEQhrP9-mY,3414
38
- datarobot_genai/drmcp/core/mcp_instance.py,sha256=hArS-BIdsIdRyRA21a4_ILgqqzmuRxZts-Ewgtf1H60,20917
39
- datarobot_genai/drmcp/core/mcp_server_tools.py,sha256=odNZKozfx0VV38SLZHw9lY0C0JM_JnRI06W3BBXnyE4,4278
38
+ datarobot_genai/drmcp/core/mcp_instance.py,sha256=nt4gOlAQklMcqmohRIKovYcyhgLdb08NHMo28DBYmOk,18362
40
39
  datarobot_genai/drmcp/core/routes.py,sha256=dqE2M0UzAyyN9vQjlyTjYW4rpju3LT039po5weuO__I,17936
41
40
  datarobot_genai/drmcp/core/routes_utils.py,sha256=vSseXWlplMSnRgoJgtP_rHxWSAVYcx_tpTv4lyTpQoc,944
42
41
  datarobot_genai/drmcp/core/server_life_cycle.py,sha256=WKGJWGxalvqxupzJ2y67Kklc_9PgpZT0uyjlv_sr5wc,3419
43
42
  datarobot_genai/drmcp/core/telemetry.py,sha256=NEkSTC1w6uQgtukLHI-sWvR4EMgInysgATcvfQ5CplM,15378
44
43
  datarobot_genai/drmcp/core/tool_config.py,sha256=5JCWO70ZH-K-34yS7vYJG2nl4i9UO_q_W9NCoWSXXno,3271
45
- datarobot_genai/drmcp/core/tool_filter.py,sha256=tLOcG50QBvS48cOVHM6OqoODYiiS6KeM_F-2diaHkW0,2858
44
+ datarobot_genai/drmcp/core/tool_filter.py,sha256=yKQlEtzyIeXGxZJkHbK36QI19vmgQkvqmfx5cTo2pp4,3156
46
45
  datarobot_genai/drmcp/core/utils.py,sha256=EvfpqKZ3tECMoxpIQ_tA_3rOgy6KJEYKC0lWZo_Daag,4517
47
46
  datarobot_genai/drmcp/core/dynamic_prompts/__init__.py,sha256=y4yapzp3KnFMzSR6HlNDS4uSuyNT7I1iPBvaCLsS0sU,577
48
47
  datarobot_genai/drmcp/core/dynamic_prompts/controllers.py,sha256=AGJlKqgHRO0Kd7Gl-Ulw9KYBgzjTTFXWBvOUF-SuKUI,5454
@@ -95,7 +94,7 @@ datarobot_genai/drmcp/tools/predictive/deployment_info.py,sha256=BGEF_dmbxOBJR0n
95
94
  datarobot_genai/drmcp/tools/predictive/model.py,sha256=Yih5-KedJ-1yupPLXCJsCXOdyWWi9pRvgapXDlgXWJA,4891
96
95
  datarobot_genai/drmcp/tools/predictive/predict.py,sha256=Qoob2_t2crfWtyPzkXMRz2ITZumnczU6Dq4C7q9RBMI,9370
97
96
  datarobot_genai/drmcp/tools/predictive/predict_realtime.py,sha256=urq6rPyZFsAP-bPyclSNzrkvb6FTamdlFau8q0IWWJ0,13472
98
- datarobot_genai/drmcp/tools/predictive/project.py,sha256=KaMDAvJY4s12j_4ybA7-KcCS1yMOj-KPIKNBgCSE2iM,2536
97
+ datarobot_genai/drmcp/tools/predictive/project.py,sha256=xC52UdYvuFeNZC7Y5MfXcvzTL70WwAacQXESr6rqN6s,3255
99
98
  datarobot_genai/drmcp/tools/predictive/training.py,sha256=S9V7AlO6mAgIAJNww0g5agFOw4YqRiCsIGaRDJcOe4A,23991
100
99
  datarobot_genai/langgraph/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
101
100
  datarobot_genai/langgraph/agent.py,sha256=DRnywmS9KDywyChtuIZZwNKbJs8BpC259EG_kxYbiQ8,15828
@@ -111,9 +110,9 @@ datarobot_genai/nat/datarobot_llm_clients.py,sha256=Yu208Ed_p_4P3HdpuM7fYnKcXtim
111
110
  datarobot_genai/nat/datarobot_llm_providers.py,sha256=aDoQcTeGI-odqydPXEX9OGGNFbzAtpqzTvHHEkmJuEQ,4963
112
111
  datarobot_genai/nat/datarobot_mcp_client.py,sha256=35FzilxNp4VqwBYI0NsOc91-xZm1C-AzWqrOdDy962A,9612
113
112
  datarobot_genai/nat/helpers.py,sha256=Q7E3ADZdtFfS8E6OQPyw2wgA6laQ58N3bhLj5CBWwJs,3265
114
- datarobot_genai-0.2.24.dist-info/METADATA,sha256=-QGxEqh8oSYn-gL93kIxqGhOTkrlHQRMf4HqMYTyfDs,6301
115
- datarobot_genai-0.2.24.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
116
- datarobot_genai-0.2.24.dist-info/entry_points.txt,sha256=jEW3WxDZ8XIK9-ISmTyt5DbmBb047rFlzQuhY09rGrM,284
117
- datarobot_genai-0.2.24.dist-info/licenses/AUTHORS,sha256=isJGUXdjq1U7XZ_B_9AH8Qf0u4eX0XyQifJZ_Sxm4sA,80
118
- datarobot_genai-0.2.24.dist-info/licenses/LICENSE,sha256=U2_VkLIktQoa60Nf6Tbt7E4RMlfhFSjWjcJJfVC-YCE,11341
119
- datarobot_genai-0.2.24.dist-info/RECORD,,
113
+ datarobot_genai-0.2.26.dist-info/METADATA,sha256=7DMbKQ_9IV0Hmg8xCubJfPJ37C-TZ1-hr0RMgnsUUOQ,6301
114
+ datarobot_genai-0.2.26.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
115
+ datarobot_genai-0.2.26.dist-info/entry_points.txt,sha256=jEW3WxDZ8XIK9-ISmTyt5DbmBb047rFlzQuhY09rGrM,284
116
+ datarobot_genai-0.2.26.dist-info/licenses/AUTHORS,sha256=isJGUXdjq1U7XZ_B_9AH8Qf0u4eX0XyQifJZ_Sxm4sA,80
117
+ datarobot_genai-0.2.26.dist-info/licenses/LICENSE,sha256=U2_VkLIktQoa60Nf6Tbt7E4RMlfhFSjWjcJJfVC-YCE,11341
118
+ datarobot_genai-0.2.26.dist-info/RECORD,,
@@ -1,129 +0,0 @@
1
- # Copyright 2025 DataRobot, Inc.
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # http://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
-
15
- import logging
16
-
17
- from .mcp_instance import dr_core_mcp_tool
18
- from .mcp_instance import mcp
19
-
20
- logger = logging.getLogger(__name__)
21
-
22
-
23
- @dr_core_mcp_tool(tags={"mcp_server_tools", "metadata"})
24
- async def get_all_available_tags() -> str:
25
- """
26
- List all unique tags from all registered tools.
27
-
28
- Returns
29
- -------
30
- A string containing all available tags, one per line.
31
- """
32
- tags = await mcp.get_all_tags()
33
- if not tags:
34
- return "No tags found in any tools."
35
-
36
- return "\n".join(sorted(tags))
37
-
38
-
39
- @dr_core_mcp_tool(tags={"mcp_server_tools", "metadata", "discovery"})
40
- async def list_tools_by_tags(tags: list[str] | None = None, match_all: bool = False) -> str:
41
- """
42
- List tools filtered by tags.
43
-
44
- Args:
45
- tags: Optional list of tags to filter by. If None, returns all tools.
46
- match_all: If True, tool must have all specified tags (AND logic).
47
- If False, tool must have at least one tag (OR logic).
48
- Only used when tags is provided.
49
-
50
- Returns
51
- -------
52
- A formatted string listing tools that match the tag criteria.
53
- """
54
- tools = await mcp.list_tools(tags=tags, match_all=match_all)
55
-
56
- if not tools:
57
- if tags:
58
- logic = "all" if match_all else "any"
59
- return f"No tools found with {logic} of the tags: {', '.join(tags)}"
60
- else:
61
- return "No tools found."
62
-
63
- result = []
64
- if tags:
65
- logic = "all" if match_all else "any"
66
- result.append(f"Tools with {logic} of the tags: {', '.join(tags)}")
67
- else:
68
- result.append("All available tools:")
69
-
70
- result.append("")
71
-
72
- for i, tool in enumerate(tools, 1):
73
- tool_tags = []
74
- if tool.annotations and hasattr(tool.annotations, "extra") and tool.annotations.extra:
75
- tool_tags = tool.annotations.extra.get("tags", [])
76
-
77
- result.append(f"{i}. {tool.name}")
78
- result.append(f" Description: {tool.description}")
79
- if tool_tags:
80
- result.append(f" Tags: {', '.join(tool_tags)}")
81
- result.append("")
82
-
83
- return "\n".join(result)
84
-
85
-
86
- @dr_core_mcp_tool(tags={"mcp_server_tools", "metadata", "discovery"})
87
- async def get_tool_info_by_name(tool_name: str) -> str:
88
- """
89
- Get detailed information about a specific tool by name.
90
-
91
- Args:
92
- tool_name: The name of the tool to get information about.
93
-
94
- Returns
95
- -------
96
- A formatted string with detailed information about the tool.
97
- """
98
- all_tools = await mcp.list_tools()
99
-
100
- for tool in all_tools:
101
- if tool.name == tool_name:
102
- result = [f"Tool: {tool.name}"]
103
- result.append(f"Description: {tool.description}")
104
-
105
- # Get tags
106
- tool_tags = []
107
- if tool.annotations and hasattr(tool.annotations, "extra") and tool.annotations.extra:
108
- tool_tags = tool.annotations.extra.get("tags", [])
109
-
110
- if tool_tags:
111
- result.append(f"Tags: {', '.join(tool_tags)}")
112
- else:
113
- result.append("Tags: None")
114
-
115
- # Get input schema info
116
- if (
117
- tool.inputSchema
118
- and hasattr(tool.inputSchema, "properties")
119
- and tool.inputSchema.properties
120
- ):
121
- result.append("Parameters:")
122
- for param_name, param_info in tool.inputSchema.properties.items():
123
- param_type = param_info.get("type", "unknown")
124
- param_desc = param_info.get("description", "No description")
125
- result.append(f" - {param_name} ({param_type}): {param_desc}")
126
-
127
- return "\n".join(result)
128
-
129
- return f"Tool '{tool_name}' not found."