77
88from mcp .server .fastmcp import FastMCP
99from mcp .server .fastmcp import Image
10- from mcp .types import CallToolResult
11- from mcp .types import TextContent
10+ from mcp .shared .exceptions import McpError
11+ from mcp .types import ErrorData
12+ from mcp .types import INTERNAL_ERROR
1213import optuna
1314import optuna_dashboard
1415import plotly
@@ -112,23 +113,21 @@ def create_study(
112113 return StudyResponse (study_name = study_name )
113114
114115 @mcp .tool (structured_output = True )
115- def get_all_study_names () -> list [StudyResponse ] | CallToolResult :
116+ def get_all_study_names () -> list [StudyResponse ]:
116117 """Get all study names from the storage."""
117118 storage : str | optuna .storages .BaseStorage | None = None
118119 if mcp .study is not None :
119120 storage = mcp .study ._storage
120121 elif mcp .storage is not None :
121122 storage = mcp .storage
122123 else :
123- return CallToolResult (
124- content = [TextContent (type = "text" , text = "No storage specified." )], isError = True
125- )
124+ raise McpError (ErrorData (code = INTERNAL_ERROR , message = "No storage specified." ))
126125
127126 study_names = optuna .get_all_study_names (storage )
128127 return [StudyResponse (study_name = name ) for name in study_names ]
129128
130129 @mcp .tool (structured_output = True )
131- def ask (search_space : dict ) -> TrialResponse | CallToolResult :
130+ def ask (search_space : dict ) -> TrialResponse :
132131 """Suggest new parameters using Optuna
133132
134133 search_space must be a string that can be evaluated to a dictionary to specify Optuna's distributions.
@@ -142,18 +141,14 @@ def ask(search_space: dict) -> TrialResponse | CallToolResult:
142141 for name , dist in search_space .items ()
143142 }
144143 except Exception as e :
145- return CallToolResult (
146- content = [TextContent (type = "text" , text = f"Error: { e } " )], isError = True
147- )
144+ raise McpError (ErrorData (code = INTERNAL_ERROR , message = f"Error: { e } " )) from e
148145
149146 if mcp .study is None :
150- return CallToolResult (
151- content = [
152- TextContent (
153- type = "text" , text = "No study has been created. Please create a study first."
154- )
155- ],
156- isError = True ,
147+ raise McpError (
148+ ErrorData (
149+ code = INTERNAL_ERROR ,
150+ message = "No study has been created. Please create a study first." ,
151+ )
157152 )
158153
159154 trial = mcp .study .ask (fixed_distributions = distributions )
@@ -164,16 +159,14 @@ def ask(search_space: dict) -> TrialResponse | CallToolResult:
164159 )
165160
166161 @mcp .tool (structured_output = True )
167- def tell (trial_number : int , values : float | list [float ]) -> TrialResponse | CallToolResult :
162+ def tell (trial_number : int , values : float | list [float ]) -> TrialResponse :
168163 """Report the result of a trial"""
169164 if mcp .study is None :
170- return CallToolResult (
171- content = [
172- TextContent (
173- type = "text" , text = "No study has been created. Please create a study first."
174- )
175- ],
176- isError = True ,
165+ raise McpError (
166+ ErrorData (
167+ code = INTERNAL_ERROR ,
168+ message = "No study has been created. Please create a study first." ,
169+ )
177170 )
178171
179172 mcp .study .tell (
@@ -190,7 +183,7 @@ def tell(trial_number: int, values: float | list[float]) -> TrialResponse | Call
190183 @mcp .tool (structured_output = True )
191184 def set_sampler (
192185 name : typing .Literal ["TPESampler" , "NSGAIISampler" , "RandomSampler" , "GPSampler" ],
193- ) -> StudyResponse | CallToolResult :
186+ ) -> StudyResponse :
194187 """Set the sampler for the study.
195188 The sampler must be one of the following:
196189 - TPESampler
@@ -204,33 +197,28 @@ def set_sampler(
204197 """
205198 sampler = getattr (optuna .samplers , name )()
206199 if mcp .study is None :
207- return CallToolResult (
208- content = [
209- TextContent (
210- type = "text" , text = "No study has been created. Please create a study first."
211- )
212- ],
213- isError = True ,
200+ raise McpError (
201+ ErrorData (
202+ code = INTERNAL_ERROR ,
203+ message = "No study has been created. Please create a study first." ,
204+ )
214205 )
206+
215207 mcp .study .sampler = sampler
216208 return StudyResponse (
217209 study_name = mcp .study .study_name ,
218210 sampler_name = name ,
219211 )
220212
221213 @mcp .tool (structured_output = True )
222- def set_trial_user_attr (
223- trial_number : int , key : str , value : typing .Any
224- ) -> str | CallToolResult :
214+ def set_trial_user_attr (trial_number : int , key : str , value : typing .Any ) -> str :
225215 """Set user attributes for a trial"""
226216 if mcp .study is None :
227- return CallToolResult (
228- content = [
229- TextContent (
230- type = "text" , text = "No study has been created. Please create a study first."
231- )
232- ],
233- isError = True ,
217+ raise McpError (
218+ ErrorData (
219+ code = INTERNAL_ERROR ,
220+ message = "No study has been created. Please create a study first." ,
221+ )
234222 )
235223
236224 storage = mcp .study ._storage
@@ -241,17 +229,16 @@ def set_trial_user_attr(
241229 return f"User attribute { key } set to { json .dumps (value )} for trial { trial_number } "
242230
243231 @mcp .tool (structured_output = True )
244- def get_trial_user_attrs (trial_number : int ) -> TrialResponse | CallToolResult :
232+ def get_trial_user_attrs (trial_number : int ) -> TrialResponse :
245233 """Get user attributes in a trial"""
246234 if mcp .study is None :
247- return CallToolResult (
248- content = [
249- TextContent (
250- type = "text" , text = "No study has been created. Please create a study first."
251- )
252- ],
253- isError = True ,
235+ raise McpError (
236+ ErrorData (
237+ code = INTERNAL_ERROR ,
238+ message = "No study has been created. Please create a study first." ,
239+ )
254240 )
241+
255242 storage = mcp .study ._storage
256243 trial_id = storage .get_trial_id_from_study_id_trial_number (
257244 mcp .study ._study_id , trial_number
@@ -263,7 +250,7 @@ def get_trial_user_attrs(trial_number: int) -> TrialResponse | CallToolResult:
263250 )
264251
265252 @mcp .tool (structured_output = True )
266- def set_metric_names (metric_names : list [str ]) -> str | CallToolResult :
253+ def set_metric_names (metric_names : list [str ]) -> str :
267254 """Set metric_names. metric_names are labels used to distinguish what each objective value is.
268255
269256 Args:
@@ -272,78 +259,72 @@ def set_metric_names(metric_names: list[str]) -> str | CallToolResult:
272259 The length of metric_names list must be the same with the number of objectives.
273260 """
274261 if mcp .study is None :
275- return CallToolResult (
276- content = [
277- TextContent (
278- type = "text" , text = "No study has been created. Please create a study first."
279- )
280- ],
281- isError = True ,
262+ raise McpError (
263+ ErrorData (
264+ code = INTERNAL_ERROR ,
265+ message = "No study has been created. Please create a study first." ,
266+ )
282267 )
268+
283269 mcp .study .set_metric_names (metric_names )
284270 return f"metric_names set to { json .dumps (metric_names )} "
285271
286272 @mcp .tool (structured_output = True )
287- def get_metric_names () -> str | CallToolResult :
273+ def get_metric_names () -> str :
288274 """Get metric_names"""
289275 if mcp .study is None :
290- return CallToolResult (
291- content = [
292- TextContent (
293- type = "text" , text = "No study has been created. Please create a study first."
294- )
295- ],
296- isError = True ,
276+ raise McpError (
277+ ErrorData (
278+ code = INTERNAL_ERROR ,
279+ message = "No study has been created. Please create a study first." ,
280+ )
297281 )
282+
298283 return f"Metric names: { json .dumps (mcp .study .metric_names )} "
299284
300285 @mcp .tool (structured_output = True )
301- def get_directions () -> StudyResponse | CallToolResult :
286+ def get_directions () -> StudyResponse :
302287 """Get the directions of the study."""
303288 if mcp .study is None :
304- return CallToolResult (
305- content = [
306- TextContent (
307- type = "text" , text = "No study has been created. Please create a study first."
308- )
309- ],
310- isError = True ,
289+ raise McpError (
290+ ErrorData (
291+ code = INTERNAL_ERROR ,
292+ message = "No study has been created. Please create a study first." ,
293+ )
311294 )
295+
312296 directions = [d .name .lower () for d in mcp .study .directions ]
313297 return StudyResponse (
314298 study_name = mcp .study .study_name ,
315299 directions = directions ,
316300 )
317301
318302 @mcp .tool (structured_output = True )
319- def get_trials () -> str | CallToolResult :
303+ def get_trials () -> str :
320304 """Get all trials in a CSV format"""
321305 if mcp .study is None :
322- return CallToolResult (
323- content = [
324- TextContent (
325- type = "text" , text = "No study has been created. Please create a study first."
326- )
327- ],
328- isError = True ,
306+ raise McpError (
307+ ErrorData (
308+ code = INTERNAL_ERROR ,
309+ message = "No study has been created. Please create a study first." ,
310+ )
329311 )
312+
330313 csv_string = mcp .study .trials_dataframe ().to_csv ()
331314 return f"Trials: \n { csv_string } "
332315
333316 @mcp .tool (structured_output = True )
334- def best_trial () -> TrialResponse | CallToolResult :
317+ def best_trial () -> TrialResponse :
335318 """Get the best trial
336319
337320 This feature can only be used for single-objective optimization. If your study is multi-objective, use best_trials instead.
338321 """
339322 if mcp .study is None :
340- return CallToolResult (
341- content = [
342- TextContent (
343- type = "text" , text = "No study has been created. Please create a study first."
344- )
345- ],
346- isError = True ,
323+ raise McpError (
324+ ErrorData (
325+ code = INTERNAL_ERROR ,
326+ message = "No study has been created. Please create a study first." ,
327+ )
347328 )
348329
349330 trial = mcp .study .best_trial
@@ -356,17 +337,16 @@ def best_trial() -> TrialResponse | CallToolResult:
356337 )
357338
358339 @mcp .tool (structured_output = True )
359- def best_trials () -> list [TrialResponse ] | CallToolResult :
340+ def best_trials () -> list [TrialResponse ]:
360341 """Return trials located at the Pareto front in the study."""
361342 if mcp .study is None :
362- return CallToolResult (
363- content = [
364- TextContent (
365- type = "text" , text = "No study has been created. Please create a study first."
366- )
367- ],
368- isError = True ,
343+ raise McpError (
344+ ErrorData (
345+ code = INTERNAL_ERROR ,
346+ message = "No study has been created. Please create a study first." ,
347+ )
369348 )
349+
370350 return [
371351 TrialResponse (
372352 trial_number = trial .number ,
@@ -393,33 +373,31 @@ def _create_trial(trial: TrialToAdd) -> optuna.trial.FrozenTrial:
393373 )
394374
395375 @mcp .tool (structured_output = True )
396- def add_trial (trial : TrialToAdd ) -> str | CallToolResult :
376+ def add_trial (trial : TrialToAdd ) -> str :
397377 """Add a trial to the study."""
398378 if mcp .study is None :
399- return CallToolResult (
400- content = [
401- TextContent (
402- type = "text" , text = "No study has been created. Please create a study first."
403- )
404- ],
405- isError = True ,
379+ raise McpError (
380+ ErrorData (
381+ code = INTERNAL_ERROR ,
382+ message = "No study has been created. Please create a study first." ,
383+ )
406384 )
385+
407386 mcp .study .add_trial (_create_trial (trial ))
408387 return "Trial was added."
409388
410389 @mcp .tool (structured_output = True )
411- def add_trials (trials : list [TrialToAdd ]) -> str | CallToolResult :
390+ def add_trials (trials : list [TrialToAdd ]) -> str :
412391 """Add multiple trials to the study."""
413392 frozen_trials = [_create_trial (trial ) for trial in trials ]
414393 if mcp .study is None :
415- return CallToolResult (
416- content = [
417- TextContent (
418- type = "text" , text = "No study has been created. Please create a study first."
419- )
420- ],
421- isError = True ,
394+ raise McpError (
395+ ErrorData (
396+ code = INTERNAL_ERROR ,
397+ message = "No study has been created. Please create a study first." ,
398+ )
422399 )
400+
423401 mcp .study .add_trials (frozen_trials )
424402 return f"{ len (trials )} trials were added."
425403
@@ -640,7 +618,7 @@ def plot_rank(
640618 return Image (data = plotly .io .to_image (fig ), format = "png" )
641619
642620 @mcp .tool (structured_output = True )
643- def launch_optuna_dashboard (port : int = 58080 ) -> str | CallToolResult :
621+ def launch_optuna_dashboard (port : int = 58080 ) -> str :
644622 """Launch the Optuna dashboard"""
645623 storage : str | optuna .storages .BaseStorage | None = None
646624 if mcp .dashboard_thread_port is not None :
@@ -651,14 +629,11 @@ def launch_optuna_dashboard(port: int = 58080) -> str | CallToolResult:
651629 elif mcp .storage is not None :
652630 storage = mcp .storage
653631 else :
654- return CallToolResult (
655- content = [
656- TextContent (
657- type = "text" ,
658- text = "No study has been created or no storage URL has been provided. Please create a study or provide a storage URL directly." ,
659- )
660- ],
661- isError = True ,
632+ raise McpError (
633+ ErrorData (
634+ code = INTERNAL_ERROR ,
635+ message = "No study has been created. Please create a study first." ,
636+ )
662637 )
663638
664639 def runner (storage : optuna .storages .BaseStorage | str , port : int ) -> None :
0 commit comments