mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-24 06:24:18 +02:00
Fix dev apps form: union types, textarea support, JSON parsing
This commit is contained in:
parent
b92f5662d7
commit
0cd95719b6
2 changed files with 88 additions and 6 deletions
|
|
@ -871,6 +871,30 @@ def _model_from_schema(tool_name: str, input_schema: dict[str, Any]) -> type[Any
|
|||
field_definitions: dict[str, Any] = {}
|
||||
for prop_name, prop in properties.items():
|
||||
json_type = prop.get("type", "string")
|
||||
|
||||
# Handle anyOf / oneOf (union types like str | dict | None)
|
||||
for key in ("anyOf", "oneOf"):
|
||||
if key in prop:
|
||||
non_null = [
|
||||
t
|
||||
for t in prop[key]
|
||||
if isinstance(t, dict) and t.get("type") != "null"
|
||||
]
|
||||
if non_null:
|
||||
types = [t.get("type") for t in non_null if "type" in t]
|
||||
for candidate in (
|
||||
"object",
|
||||
"array",
|
||||
"integer",
|
||||
"number",
|
||||
"boolean",
|
||||
"string",
|
||||
):
|
||||
if candidate in types:
|
||||
json_type = candidate
|
||||
break
|
||||
break
|
||||
|
||||
match json_type:
|
||||
case "integer":
|
||||
py_type: type = int
|
||||
|
|
@ -878,6 +902,9 @@ def _model_from_schema(tool_name: str, input_schema: dict[str, Any]) -> type[Any
|
|||
py_type = float
|
||||
case "boolean":
|
||||
py_type = bool
|
||||
case "object" | "array":
|
||||
# Render as a string textarea; api_launch parses JSON later
|
||||
py_type = str
|
||||
case _:
|
||||
py_type = str
|
||||
|
||||
|
|
@ -897,10 +924,20 @@ def _model_from_schema(tool_name: str, input_schema: dict[str, Any]) -> type[Any
|
|||
from typing import Literal
|
||||
|
||||
py_type = Literal[tuple(prop["enum"])] # type: ignore[assignment]
|
||||
if prop.get("format") == "textarea" or (
|
||||
isinstance(prop.get("json_schema_extra"), dict)
|
||||
and prop["json_schema_extra"].get("ui", {}).get("type") == "textarea"
|
||||
):
|
||||
|
||||
# Textarea detection:
|
||||
# 1. Explicit format: "textarea" in JSON schema
|
||||
# 2. UI annotation: {"ui": {"type": "textarea"}} (json_schema_extra merged flat)
|
||||
# 3. Object/array types need multiline JSON editing
|
||||
use_textarea = (
|
||||
prop.get("format") == "textarea"
|
||||
or (
|
||||
isinstance(prop.get("ui"), dict)
|
||||
and prop["ui"].get("type") == "textarea"
|
||||
)
|
||||
or json_type in ("object", "array")
|
||||
)
|
||||
if use_textarea:
|
||||
extra["json_schema_extra"] = {"ui": {"type": "textarea"}}
|
||||
|
||||
field_definitions[prop_name] = (
|
||||
|
|
@ -1219,8 +1256,21 @@ def _make_dev_app(
|
|||
"""Picker form submits here; returns a /launch URL string for OpenLink."""
|
||||
data = await request.json()
|
||||
tool = data.pop("tool", "")
|
||||
# Remaining keys are tool arguments; pass all including empty optionals
|
||||
tool_args = dict(data)
|
||||
# Remaining keys are tool arguments. Form inputs always produce
|
||||
# strings, but some parameters expect dicts/lists — try to parse
|
||||
# string values that look like JSON objects or arrays.
|
||||
tool_args: dict[str, Any] = {}
|
||||
for k, v in data.items():
|
||||
if isinstance(v, str):
|
||||
stripped = v.strip()
|
||||
if stripped and stripped[0] in ("{", "["):
|
||||
try:
|
||||
parsed = json.loads(stripped)
|
||||
if isinstance(parsed, (dict, list)):
|
||||
v = parsed
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
pass
|
||||
tool_args[k] = v
|
||||
args_json = quote(json.dumps(tool_args))
|
||||
url = f"/launch?tool={tool}&args={args_json}"
|
||||
return Response(
|
||||
|
|
|
|||
32
src/fastmcp/types.py
Normal file
32
src/fastmcp/types.py
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
"""Reusable type annotations for FastMCP tool parameters.
|
||||
|
||||
These types can be used in tool function signatures to influence how
|
||||
parameters are presented in UIs (e.g. ``fastmcp dev apps``) and
|
||||
serialized in JSON Schema.
|
||||
|
||||
Example::
|
||||
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.types import Textarea
|
||||
|
||||
mcp = FastMCP("demo")
|
||||
|
||||
@mcp.tool()
|
||||
def run_query(sql: Textarea) -> str:
|
||||
...
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Annotated
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
Textarea = Annotated[str, Field(json_schema_extra={"format": "textarea"})]
|
||||
"""A string rendered as a multiline textarea in form-based UIs.
|
||||
|
||||
Produces ``"format": "textarea"`` in the JSON Schema, which
|
||||
``fastmcp dev apps`` picks up automatically.
|
||||
"""
|
||||
|
||||
__all__ = ["Textarea"]
|
||||
Loading…
Add table
Add a link
Reference in a new issue