Rewrite MCP tool docstrings to survive Gradio's schema parser
Browse filesThe four gr.Interface functions are exposed as MCP tools (mcp_server=True),
whose schema is built from each function's docstring + type hints. Gradio's
parser silently mangles anything off-shape, so the tools' schemas were broken:
- video_inference: had no Args section (undocumented params) and a vague
output description; added full output schema, named bbox format, and hints.
- video_annotation: had no docstring at all; added one.
- image_visual_inference: multi-line JSON arg descriptions produced phantom
parameters; collapsed to single-line args and moved schema into prose.
- image_text_inference: Returns section detail was discarded by the parser;
folded field names/output schema into the description prose.
Per the gradio-mcp-tool-docstrings skill: full output schema (with the
albumentations bbox-format URL) lives in the description prose, one line per
Args entry, no Returns section, and real return type hints added. Verified
against a live /gradio_api/mcp/schema dump: correct tool names, complete
descriptions with URLs intact, all args documented, no phantom params.
No runtime behavior or signatures changed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@@ -164,10 +164,16 @@ def video_inference(
|
|
| 164 |
prompt: str,
|
| 165 |
timeout_duration: int = 60,
|
| 166 |
annotation_mode: bool = False,
|
| 167 |
-
):
|
| 168 |
-
"""
|
| 169 |
-
|
| 170 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 171 |
"""
|
| 172 |
assert type(VID_MODEL) != type(None) and type(VID_PROCESSOR) != type(
|
| 173 |
None
|
|
@@ -262,7 +268,16 @@ def video_inference(
|
|
| 262 |
)
|
| 263 |
|
| 264 |
|
| 265 |
-
def video_annotation(input_video, prompt: str, timeout_duration: int = 60):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 266 |
return video_inference(
|
| 267 |
input_video, prompt, timeout_duration=timeout_duration, annotation_mode=True
|
| 268 |
)
|
|
@@ -275,22 +290,17 @@ def image_visual_inference(
|
|
| 275 |
bboxes=None,
|
| 276 |
points=None,
|
| 277 |
point_labels=None,
|
| 278 |
-
):
|
| 279 |
-
"""
|
| 280 |
-
SAM3 image segmentation with visual prompts (points and/or boxes).
|
| 281 |
|
| 282 |
-
|
| 283 |
|
| 284 |
Args:
|
| 285 |
-
im:
|
| 286 |
-
variant:
|
| 287 |
-
bboxes:
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
[{"x":..., "y":...}, ...] -- all points describe a single object/mask
|
| 291 |
-
point_labels: list of ints (or JSON string), 1=foreground, 0=background
|
| 292 |
-
Returns:
|
| 293 |
-
list: a list of base64-encoded mask strings (one per box, then one for points)
|
| 294 |
"""
|
| 295 |
assert TRK_MODEL is not None and TRK_PROCESSOR is not None, (
|
| 296 |
"Image tracker model failed to load on startup."
|
|
@@ -345,17 +355,17 @@ def image_visual_inference(
|
|
| 345 |
|
| 346 |
|
| 347 |
@spaces.GPU
|
| 348 |
-
def image_text_inference(
|
| 349 |
-
|
| 350 |
-
|
|
|
|
|
|
|
|
|
|
| 351 |
|
| 352 |
Args:
|
| 353 |
-
im:
|
| 354 |
-
prompt: concept to segment, e.g. "player in white"
|
| 355 |
-
conf_threshold: score
|
| 356 |
-
Returns:
|
| 357 |
-
list: detection dicts [{"track_id":..., "x":..., "y":..., "w":..., "h":...,
|
| 358 |
-
"conf":..., "mask_b64":...}, ...] with normalized x/y/w/h
|
| 359 |
"""
|
| 360 |
assert IMG_MODEL is not None and IMG_PROCESSOR is not None, (
|
| 361 |
"Image text model failed to load on startup."
|
|
|
|
| 164 |
prompt: str,
|
| 165 |
timeout_duration: int = 60,
|
| 166 |
annotation_mode: bool = False,
|
| 167 |
+
) -> list[dict] | str:
|
| 168 |
+
"""Track and segment objects across a video with SAM3 using a natural-language text prompt, returning per-object-per-frame detections (or an annotated video).
|
| 169 |
+
|
| 170 |
+
The prompt is a concept to find (e.g. "player in white", "red car"); every matching instance is tracked across all frames with a stable track_id. This tool has two output shapes selected by annotation_mode. When annotation_mode is false (default) it returns a JSON list of detection objects, one per tracked object per frame. Each detection has: "frame" (integer, 0-based frame index); "track_id" (integer, stable across frames for one object); "x", "y", "w", "h" (floats); "conf" (float, always 1 for video); and "mask_b64" (string). The bounding box ("x","y","w","h") is NORMALIZED 0-1: (x, y) is the top-left corner and (w, h) is the box size, each divided by the video width/height -- i.e. the albumentations "coco" layout [x_min, y_min, width, height] but normalized to 0-1 rather than absolute pixels, and NOT the "pascal_voc" [x_min, y_min, x_max, y_max] layout. Bounding-box formats are documented at https://albumentations.ai/docs/3-basic-usage/bounding-boxes-augmentations/#bounding-box-formats . "mask_b64" is a base64-encoded 1-bit PNG of the binary segmentation mask at the frame resolution; decode it with PIL, e.g. numpy.array(Image.open(io.BytesIO(base64.b64decode(mask_b64)))) to get a 0/255 mask. When annotation_mode is true it instead returns a filesystem path to an mp4 video with the colored mask overlays burned in.
|
| 171 |
+
|
| 172 |
+
Args:
|
| 173 |
+
input_video: The input video to segment (a file path, or an uploaded-file object with a "name" key).
|
| 174 |
+
prompt: Natural-language description of the object(s) to track/segment, e.g. "player in white".
|
| 175 |
+
timeout_duration: Max GPU lease in seconds for this request (60, 120, 180, or 240); longer videos need more.
|
| 176 |
+
annotation_mode: If false (default) return the JSON detections list; if true return a path to an annotated mp4 with mask overlays.
|
| 177 |
"""
|
| 178 |
assert type(VID_MODEL) != type(None) and type(VID_PROCESSOR) != type(
|
| 179 |
None
|
|
|
|
| 268 |
)
|
| 269 |
|
| 270 |
|
| 271 |
+
def video_annotation(input_video, prompt: str, timeout_duration: int = 60) -> str:
|
| 272 |
+
"""Track and segment objects across a video with SAM3 using a natural-language text prompt, and return an annotated video with the segmentation masks overlaid.
|
| 273 |
+
|
| 274 |
+
The prompt is a concept to find (e.g. "player in white", "red car"); every matching instance is tracked across all frames and rendered as a colored mask overlay (color keyed by object). This is the annotated-video counterpart of video_inference: it returns a filesystem path to an mp4 with the mask overlays burned in, rather than JSON detections. Use video_inference (annotation_mode false) instead if you need the structured per-frame bounding boxes and base64 masks.
|
| 275 |
+
|
| 276 |
+
Args:
|
| 277 |
+
input_video: The input video to segment (a file path, or an uploaded-file object with a "name" key).
|
| 278 |
+
prompt: Natural-language description of the object(s) to track/segment, e.g. "player in white".
|
| 279 |
+
timeout_duration: Max GPU lease in seconds for this request (60, 120, 180, or 240); longer videos need more.
|
| 280 |
+
"""
|
| 281 |
return video_inference(
|
| 282 |
input_video, prompt, timeout_duration=timeout_duration, annotation_mode=True
|
| 283 |
)
|
|
|
|
| 290 |
bboxes=None,
|
| 291 |
points=None,
|
| 292 |
point_labels=None,
|
| 293 |
+
) -> list[str]:
|
| 294 |
+
"""Segment objects in a single image with SAM3 using visual prompts (bounding boxes and/or points); a drop-in match for SAM2's process_image.
|
|
|
|
| 295 |
|
| 296 |
+
Provide at least one of bboxes or points. Each bounding box produces its own mask; all points together describe one object and produce a single mask. The return value is a JSON list of base64-encoded 1-bit-PNG mask strings, ordered as: one mask per bounding box (in the order the boxes were given), then, if points were provided, one final mask for the points prompt. Each string decodes to a binary segmentation mask at the input image resolution; decode it with PIL, e.g. numpy.array(Image.open(io.BytesIO(base64.b64decode(mask_b64)))) to get a 0/255 mask. Box pixel coordinates use (x0, y0) = top-left and (x1, y1) = bottom-right corners of the ORIGINAL input image (albumentations "pascal_voc" [x_min, y_min, x_max, y_max], absolute pixels); point coordinates (x, y) are absolute pixels in the same image. Coordinate formats are documented at https://albumentations.ai/docs/3-basic-usage/bounding-boxes-augmentations/#bounding-box-formats .
|
| 297 |
|
| 298 |
Args:
|
| 299 |
+
im: The RGB image to segment (a PIL Image).
|
| 300 |
+
variant: Accepted for SAM2 API parity and ignored; SAM3 has a single model.
|
| 301 |
+
bboxes: Bounding-box prompts as a list of dicts (or a JSON string), each dict {"x0","y0","x1","y1"} in absolute pixels; one output mask per box.
|
| 302 |
+
points: Point prompts as a list of dicts (or a JSON string), each dict {"x","y"} in absolute pixels; all points together define a single object/mask.
|
| 303 |
+
point_labels: List of ints (or a JSON string) parallel to points, 1=foreground and 0=background; required whenever points is given.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 304 |
"""
|
| 305 |
assert TRK_MODEL is not None and TRK_PROCESSOR is not None, (
|
| 306 |
"Image tracker model failed to load on startup."
|
|
|
|
| 355 |
|
| 356 |
|
| 357 |
@spaces.GPU
|
| 358 |
+
def image_text_inference(
|
| 359 |
+
im: Image.Image, prompt: str, conf_threshold: float = 0.5
|
| 360 |
+
) -> list[dict]:
|
| 361 |
+
"""Segment every instance of a concept in a single image with SAM3 using a natural-language text prompt (concept segmentation).
|
| 362 |
+
|
| 363 |
+
The prompt is a concept to find (e.g. "player in white", "red car"); each matching instance becomes one detection. Returns a JSON list of detection objects, each with: "track_id" (integer index of the instance within this image); "x", "y", "w", "h" (floats); "conf" (float 0-1 detection score); and "mask_b64" (string). The bounding box ("x","y","w","h") is NORMALIZED 0-1: (x, y) is the top-left corner and (w, h) is the box size, each divided by the image width/height -- i.e. the albumentations "coco" layout [x_min, y_min, width, height] but normalized to 0-1 rather than absolute pixels, and NOT the "pascal_voc" [x_min, y_min, x_max, y_max] layout. Bounding-box formats are documented at https://albumentations.ai/docs/3-basic-usage/bounding-boxes-augmentations/#bounding-box-formats . "mask_b64" is a base64-encoded 1-bit PNG of the binary segmentation mask at the input image resolution; decode it with PIL, e.g. numpy.array(Image.open(io.BytesIO(base64.b64decode(mask_b64)))) to get a 0/255 mask.
|
| 364 |
|
| 365 |
Args:
|
| 366 |
+
im: The RGB image to segment (a PIL Image).
|
| 367 |
+
prompt: Natural-language concept to segment, e.g. "player in white"; all matching instances are returned.
|
| 368 |
+
conf_threshold: Minimum detection score from 0.0 to 1.0; instances scoring below this are discarded.
|
|
|
|
|
|
|
|
|
|
| 369 |
"""
|
| 370 |
assert IMG_MODEL is not None and IMG_PROCESSOR is not None, (
|
| 371 |
"Image text model failed to load on startup."
|