11import functools
22import inspect
3+ from types import NoneType
4+ import typing
35
6+ from planet_mcp import models
47from planet_mcp .clients import session
58from . import descriptions
69from fastmcp import FastMCP
710import planet
8- from typing import Union , Optional
9-
10- from pydantic import PydanticSchemaGenerationError
11+ from typing import Union
1112
1213
1314# tools we don't want enabled at all.
3031 "subscriptions_update_subscription" ,
3132}
3233
34+ # tools with signatures we want to simplify for LLM usage
35+ TOOL_SIG_OVERRIDE = {
36+ "features_add_items" ,
37+ "data_get_item_coverage" ,
38+ "data_search" ,
39+ "mosaics_download_quad" ,
40+ "mosaics_download_quads" ,
41+ "mosaics_get_quad" ,
42+ "mosaics_list_quads" ,
43+ "mosaics_list_series_mosaics" ,
44+ "mosaics_summarize_quads" ,
45+ }
46+
3347SDK_CLIENTS = [
3448 (planet .FeaturesClient , "features" ),
3549 (planet .DataClient , "data" ),
@@ -85,15 +99,12 @@ def make_tools(mcp: FastMCP, client_class: type, prefix: str):
8599 if tag in full_name :
86100 opts ["tags" ].add (tag )
87101
88- try :
89- mcp .tool (func , name = full_name , ** opts )
90- except PydanticSchemaGenerationError :
91- # there's a few functions we need to modify again because of custom types.
92- modified_func = _create_param_modified_wrapper (func )
93- try :
94- mcp .tool (modified_func , name = full_name , ** opts )
95- except Exception as e :
96- print ("Unable to add tool" , full_name , e )
102+ # some tools have function signatures that need to be
103+ # modified/simplified.
104+ if full_name in TOOL_SIG_OVERRIDE :
105+ func = _create_param_modified_wrapper (func )
106+
107+ mcp .tool (func , name = full_name , ** opts )
97108
98109
99110def _async_get_wrapper (f , prefix ):
@@ -120,9 +131,14 @@ async def wrapper(*args, **kwargs):
120131
121132def _create_param_modified_wrapper (original_func ):
122133 """
123- Some functions that accept special types (typing.Protocol) fail during
124- FastMCP tool registration. This wrapper modifies the function's signature,
125- replacing the type hints with simple types that FastMCP can handle.
134+ Create a wrapper function with a modified signature using types that
135+ are compatible with FastMCP and/or easier for LLMs to work with.
136+
137+ * FastMCP tool registration doesn't support Protocol types.
138+ * the Planet SDK is flexible with geometry inputs (accepting either feature ref
139+ strings, geojson dicts or shapely-like geometries), but for LLM tool usage
140+ we generally want geojson as an object/dict. Our tests have shown better
141+ and more consistent results when tool inputs use dicts only.
126142 """
127143
128144 @functools .wraps (original_func )
@@ -139,7 +155,16 @@ async def wrapper(*args, **kwargs):
139155 if param_name in ("feature" , "quad" , "mosaic" , "series" ):
140156 wrapper .__annotations__ [param_name ] = dict
141157 elif param_name == "geometry" and "planet.models" in str (param .annotation ):
142- wrapper .__annotations__ [param_name ] = Optional [Union [dict , str ]] | None
158+ # llms should always submit geometry inputs as a geojson geometry
159+ hint = models .Geometry
160+
161+ # add None if originally used (NoneType will be an arg
162+ # within a Union type)
163+ if typing .get_origin (
164+ param .annotation
165+ ) is Union and NoneType in typing .get_args (param .annotation ):
166+ hint = hint | None
167+ wrapper .__annotations__ [param_name ] = hint
143168
144169 except Exception as e :
145170 print (f"Error modifying signature: { e } " )
0 commit comments