You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
### Step 2b: Create dynamic tools with `@fenic_tool`
142
142
143
-
Dynamic tools let you expose arbitrary Python logic as an MCP tool. They are defined with the `@fenic_tool` decorator and must return a Fenic `DataFrame`. Annotate parameters with `typing_extensions.Annotated` to provide per-argument descriptions in the tool schema. The server automatically adds `limit` and `table_format` keyword-only parameters for limiting the size of result sets and output formatting.
143
+
Dynamic tools let you expose arbitrary Python logic as an MCP tool. They are defined with the `@fenic_tool` decorator and must return a Fenic `DataFrame`. Annotate parameters with `typing_extensions.Annotated` to provide per-argument descriptions in the tool schema. The server automatically adds `limit` and `table_format` keyword-only parameters for limiting the size of result sets and output formatting -- if the tool handles its own limiting, set `client_limit_parameter` to `False` to disable this behavior. The wrapped function can be async (recommended) or synchronous.
"""Decorator to bind a DataFrame to a user-authored tool function.
111
125
126
+
Can be added to a synchronous or asynchronous (recommended) tool function.
127
+
Function based tools (dynamic tools) cannot be persisted to the catalog.
128
+
See the (Fenic MCP documentation)[https://fenic.ai/docs/topics/fenic-mcp] for more details.
129
+
112
130
Args:
113
131
tool_name: The name of the tool.
114
132
tool_description: The description of the tool.
115
-
max_result_limit: The maximum number of results to return.
133
+
max_result_limit: The maximum number of results to return. If omitted, no limit will be enforced.
134
+
client_limit_parameter: Whether to add a client-side limit parameter to the tool.
116
135
default_table_format: The default table format to return.
117
136
read_only: A hint to provide to the model that the tool does not modify its environment.
118
137
idempotent: A hint to provide to the model that calling the tool multiple times with the same input will always return the same result (redundant if read_only is True).
@@ -136,10 +155,10 @@ def find_rust(
136
155
137
156
Example: Creating an open-world tool that reaches out to an external API. The open_world flag indicates to the model that the tool may interact with an "open world" of external entities
query: Annotated[str, "Knowledge base search query"],
141
160
) -> DataFrame:
142
-
results = requests.get(...)
161
+
results = await requests.get(...)
143
162
return fc.create_dataframe(results)
144
163
145
164
Notes:
@@ -149,20 +168,26 @@ def search_knowledge_base(
149
168
- The returned object is a DynamicTool ready for registration.
150
169
- A `limit` parameter is automatically added to the function signature, which can be used to limit the number of rows returned up to the tool's `max_result_limit`.
151
170
- A `table_format` parameter is automatically added to the function signature, which can be used to specify the format of the returned data (markdown, structured)
171
+
- The `add_limit_parameter` flag can be used to control whether the client is allowed to specify a limit parameter.
"""Create a read tool over one or many datasets."""
220
+
# avoid import issue from __init__
221
+
fromfastmcp.server.contextimportContext
195
222
iflen(datasets) ==0:
196
223
raiseConfigurationError("Cannot create read tool: no datasets provided.")
197
224
@@ -296,7 +323,7 @@ async def search_summary(
296
323
)
297
324
298
325
299
-
defauto_generate_search_content_tool(
326
+
def_auto_generate_search_content_tool(
300
327
datasets: List[DatasetSpec],
301
328
session: Session,
302
329
tool_name: str,
@@ -464,13 +491,10 @@ async def analyze_func(
464
491
"- For text search, prefer regular expressions using REGEXP_MATCHES().\n",
465
492
"- Paging: use ORDER BY to define row order, then LIMIT and OFFSET for pages.\n",
466
493
f"- Results are limited to {result_limit} rows, use LIMIT/OFFSET to paginate when receiving a result set of {result_limit} or more rows.\n",
467
-
"Examples:\n", # nosec B608 - example text only
468
-
f"- SELECT * FROM {{{example_name}}} WHERE REGEXP_MATCHES(message, '(?i)error|fail') LIMIT {result_limit}",
469
-
# nosec B608 - example text only
470
-
f"- SELECT dept, COUNT(*) AS n FROM {{{example_name}}} WHERE status = 'active' GROUP BY dept HAVING n > 10 ORDER BY n DESC LIMIT {result_limit}",
471
-
# nosec B608 - example text only
472
-
f"- Paging: page 2 of size {result_limit}\n SELECT * FROM {{{example_name}}} ORDER BY created_at DESC LIMIT {result_limit} OFFSET {result_limit}",
473
-
# nosec B608 - example text only
494
+
"Examples:\n",
495
+
f"- SELECT * FROM {{{example_name}}} WHERE REGEXP_MATCHES(message, '(?i)error|fail') LIMIT {result_limit}", # nosec B608 - example text only
496
+
f"- SELECT dept, COUNT(*) AS n FROM {{{example_name}}} WHERE status = 'active' GROUP BY dept HAVING n > 10 ORDER BY n DESC LIMIT {result_limit}", # nosec B608 - example text only
497
+
f"- Paging: page 2 of size {result_limit}\n SELECT * FROM {{{example_name}}} ORDER BY created_at DESC LIMIT {result_limit} OFFSET {result_limit}", # nosec B608 - example text only
0 commit comments