Skip to content

Commit 206b76f

Browse files
committed
use internal errors and not fastmcp errors
1 parent 3a73870 commit 206b76f

File tree

5 files changed

+23
-13
lines changed

5 files changed

+23
-13
lines changed

src/fenic/api/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@
135135
"create_mcp_server",
136136
"run_mcp_server_sync",
137137
"run_mcp_server_async",
138-
"run_mcp_server_asgi"
138+
"run_mcp_server_asgi",
139139
"ToolGenerationConfig",
140140
"auto_generate_core_tools_from_tables"
141141
]

src/fenic/api/mcp/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"create_mcp_server",
1414
"run_mcp_server_sync",
1515
"run_mcp_server_async",
16-
"run_mcp_server_asgi"
16+
"run_mcp_server_asgi",
1717
"ToolGenerationConfig",
1818
"auto_generate_core_tools_from_tables"
1919
]

src/fenic/api/mcp/tool_generation.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
from typing import Dict, List, Optional, Union
1919

2020
import polars as pl
21-
from mcp.server.fastmcp.exceptions import ValidationError
2221
from typing_extensions import Annotated
2322

2423
from fenic.api.dataframe.dataframe import DataFrame
@@ -34,7 +33,7 @@
3433
from fenic.core._logical_plan.plans import InMemorySource
3534
from fenic.core._logical_plan.plans.base import LogicalPlan
3635
from fenic.core._utils.schema import convert_custom_dtype_to_polars
37-
from fenic.core.error import ConfigurationError
36+
from fenic.core.error import ConfigurationError, ValidationError
3837
from fenic.core.mcp.types import DynamicToolDefinition
3938
from fenic.core.types.datatypes import (
4039
BooleanType,
@@ -200,7 +199,7 @@ def auto_generate_search_content_tool(
200199
) -> DynamicToolDefinition:
201200
"""Create a content search tool for a single dataset (string columns)."""
202201
if len(datasets) == 0:
203-
raise ValueError("Cannot create search content tool: no datasets provided.")
202+
raise ValidationError("Cannot create search content tool: no datasets provided.")
204203

205204
name_to_df: Dict[str, DataFrame] = {d.table_name: d.df for d in datasets}
206205

@@ -558,7 +557,7 @@ def _ensure_profile_view_for_dataset(spec: DatasetSpec, tool_key: str, refresh:
558557
catalog = session._session_state.catalog
559558
if refresh or not catalog.does_view_exist(view_name):
560559
_materialize_dataset_description(spec.df, spec.table_name, view_name)
561-
return catalog.describe_view(view_name)
560+
return catalog.get_view_plan(view_name)
562561

563562
def profile_func(
564563
df_name: Annotated[str | None, "Optional DataFrame name to return a single profile for. To return profiles for all datasets, omit this parameter."] = None,

src/fenic/core/mcp/_server.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,11 +83,20 @@ def __init__(
8383
annotations = ToolAnnotations(readOnlyHint=True, openWorldHint=False)
8484
for tool in self.paramaterized_tools:
8585
tool_fn = self._build_parameterized_tool(tool)
86-
self.mcp.tool(annotations=annotations)(tool_fn)
86+
self.mcp.tool(
87+
annotations=annotations,
88+
name=_to_snake_case(tool.name),
89+
title=tool.name
90+
)(tool_fn)
8791

8892
for tool in self.dynamic_tools:
8993
tool_fn = self._register_dynamic_callable(tool)
90-
self.mcp.tool(name=self._to_snake_case(tool.name), description=tool.description, annotations=annotations)(tool_fn)
94+
self.mcp.tool(
95+
annotations=annotations,
96+
name=_to_snake_case(tool.name),
97+
title=tool.name,
98+
description=tool.description
99+
)(tool_fn)
91100

92101
async def run_async(self, transport: MCPTransport = "http", **kwargs):
93102
"""Run the MCP server asynchronously.
@@ -132,7 +141,8 @@ async def tool_fn(params: Union[ParamsModel, str]) -> MCPResultSet: # type: ign
132141
bound_plan = bind_parameters(tool._parameterized_view, payload, tool.params)
133142
async with self._collect_semaphore:
134143
pl_df, metrics = await asyncio.to_thread(lambda: self.session_state.execution.collect(bound_plan, n=effective_limit))
135-
logger.info(f"Completed query for {tool.name} in {metrics.execution_time_ms:.0f}ms with {metrics.num_output_rows} result rows.")
144+
logger.info(f"Completed query for {tool.name}")
145+
logger.info(metrics.get_summary())
136146
logger.debug(f"Query Details: {params.model_dump_json()}")
137147

138148
rows_list = pl_df.to_dicts()
@@ -150,7 +160,6 @@ async def tool_fn(params: Union[ParamsModel, str]) -> MCPResultSet: # type: ign
150160
from fastmcp.exceptions import ToolError
151161
raise ToolError(f"Fenic server failed to execute tool {tool.name}. Underlying error: {e}") from e
152162

153-
tool_fn.__name__ = _to_snake_case(tool.name)
154163
pydantic_schema_description = convert_pydantic_model_to_key_descriptions(ParamsModel)
155164
tool_fn.__doc__ = "\n\n".join([tool.description, pydantic_schema_description])
156165
return tool_fn
@@ -177,8 +186,9 @@ async def wrapper(*args, **kwargs) -> MCPResultSet:
177186
pl_df, metrics = await asyncio.to_thread(
178187
lambda: self.session_state.execution.collect(bound_plan, n=n_rows)
179188
)
180-
logger.info(f"Completed query for {tool.name} in {metrics.execution_time_ms:.0f}ms with {metrics.num_output_rows} result rows.")
181-
logger.debug(f"Query Details: {args} {kwargs}")
189+
logger.info(f"Completed query for {tool.name}")
190+
logger.info(metrics.get_summary())
191+
logger.debug(f"Query Details: {args if args else kwargs}")
182192
rows_list = pl_df.to_dicts()
183193
schema_fields = [{"name": name, "type": str(dtype)} for name, dtype in pl_df.schema.items()]
184194
table_format = "structured"

tests/api/mcp/test_tool_generation.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ def _create_table_with_rows(session, name: str, values: list[int], description:
99
df = session.create_dataframe({"id": values})
1010
# Persist table and optional description through writer (threads description into TableSink)
1111
if description is not None:
12-
df.write.save_as_table(name, mode="overwrite", description=description)
12+
df.write.save_as_table(name, mode="overwrite")
13+
session.catalog.set_table_description(name, description)
1314
else:
1415
# No description: create an empty table with schema only
1516
session.catalog.create_table(name, Schema([ColumnField("id", IntegerType)]))

0 commit comments

Comments
 (0)