bohr-agent-sdk 0.1.111__tar.gz → 0.1.112__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/PKG-INFO +1 -1
  2. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/setup.py +1 -1
  3. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/bohr_agent_sdk.egg-info/PKG-INFO +1 -1
  4. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/server/calculation_mcp_server.py +80 -92
  5. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/server/utils.py +12 -1
  6. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/README.md +0 -0
  7. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/pyproject.toml +0 -0
  8. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/setup.cfg +0 -0
  9. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/bohr_agent_sdk.egg-info/SOURCES.txt +0 -0
  10. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/bohr_agent_sdk.egg-info/dependency_links.txt +0 -0
  11. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/bohr_agent_sdk.egg-info/entry_points.txt +0 -0
  12. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/bohr_agent_sdk.egg-info/requires.txt +0 -0
  13. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/bohr_agent_sdk.egg-info/top_level.txt +0 -0
  14. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/__init__.py +0 -0
  15. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/__init__.py +0 -0
  16. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/adapter/adk/__init__.py +0 -0
  17. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/adapter/adk/client/__init__.py +0 -0
  18. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/adapter/adk/client/calculation_mcp_tool.py +0 -0
  19. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/adapter/adk/storage_artifact_service.py +0 -0
  20. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/adapter/adk/utils.py +0 -0
  21. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/adapter/camel/__init__.py +0 -0
  22. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/adapter/camel/client/__init__.py +0 -0
  23. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/adapter/camel/client/calculation_mcp_client.py +0 -0
  24. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/cli/__init__.py +0 -0
  25. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/cli/cli.py +0 -0
  26. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/cli/templates/__init__.py +0 -0
  27. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/cli/templates/calculation/simple.py.template +0 -0
  28. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/cli/templates/device/tescan_device.py.template +0 -0
  29. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/cli/templates/main.py.template +0 -0
  30. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/cli/templates/ui/__init__.py +0 -0
  31. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/cli/templates/ui/api/__init__.py +0 -0
  32. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/cli/templates/ui/api/config.py +0 -0
  33. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/cli/templates/ui/api/constants.py +0 -0
  34. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/cli/templates/ui/api/debug.py +0 -0
  35. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/cli/templates/ui/api/files.py +0 -0
  36. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/cli/templates/ui/api/files_upload.py +0 -0
  37. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/cli/templates/ui/api/files_user.py +0 -0
  38. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/cli/templates/ui/api/messages.py +0 -0
  39. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/cli/templates/ui/api/projects.py +0 -0
  40. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/cli/templates/ui/api/sessions.py +0 -0
  41. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/cli/templates/ui/api/utils.py +0 -0
  42. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/cli/templates/ui/api/websocket.py +0 -0
  43. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/cli/templates/ui/config/__init__.py +0 -0
  44. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/cli/templates/ui/config/agent_config.py +0 -0
  45. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/cli/templates/ui/frontend/index.html +0 -0
  46. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/cli/templates/ui/frontend/package.json +0 -0
  47. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/cli/templates/ui/frontend/tsconfig.json +0 -0
  48. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/cli/templates/ui/frontend/tsconfig.node.json +0 -0
  49. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/cli/templates/ui/frontend/ui-static/assets/index-DdAmKhul.js +0 -0
  50. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/cli/templates/ui/frontend/ui-static/assets/index-DfN2raU9.css +0 -0
  51. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/cli/templates/ui/frontend/ui-static/index.html +0 -0
  52. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/cli/templates/ui/frontend/vite.config.ts +0 -0
  53. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/cli/templates/ui/scripts/build_ui.py +0 -0
  54. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/cli/templates/ui/server/__init__.py +0 -0
  55. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/cli/templates/ui/server/app.py +0 -0
  56. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/cli/templates/ui/server/connection.py +0 -0
  57. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/cli/templates/ui/server/file_watcher.py +0 -0
  58. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/cli/templates/ui/server/middleware.py +0 -0
  59. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/cli/templates/ui/server/models.py +0 -0
  60. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/cli/templates/ui/server/session_manager.py +0 -0
  61. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/cli/templates/ui/server/user_files.py +0 -0
  62. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/cli/templates/ui/server/utils.py +0 -0
  63. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/cli/templates/ui/test_download.py +0 -0
  64. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/cli/templates/ui/ui_utils.py +0 -0
  65. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/cli/templates/ui/websocket-server.py +0 -0
  66. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/client/__init__.py +0 -0
  67. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/client/mcp_client.py +0 -0
  68. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/cloud/__init__.py +0 -0
  69. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/cloud/main.py +0 -0
  70. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/cloud/mcp.py +0 -0
  71. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/cloud/mqtt.py +0 -0
  72. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/device/__init__.py +0 -0
  73. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/device/device/__init__.py +0 -0
  74. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/device/device/device.py +0 -0
  75. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/device/device/types.py +0 -0
  76. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/device/mqtt_device_twin.py +0 -0
  77. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/server/__init__.py +0 -0
  78. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/server/executor/__init__.py +0 -0
  79. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/server/executor/base_executor.py +0 -0
  80. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/server/executor/dispatcher_executor.py +0 -0
  81. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/server/executor/local_executor.py +0 -0
  82. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/server/preprocessor.py +0 -0
  83. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/server/storage/__init__.py +0 -0
  84. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/server/storage/base_storage.py +0 -0
  85. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/server/storage/bohrium_storage.py +0 -0
  86. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/server/storage/http_storage.py +0 -0
  87. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/server/storage/local_storage.py +0 -0
  88. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/src/dp/agent/server/storage/oss_storage.py +0 -0
  89. {bohr_agent_sdk-0.1.111 → bohr_agent_sdk-0.1.112}/tests/test_cli.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bohr-agent-sdk
3
- Version: 0.1.111
3
+ Version: 0.1.112
4
4
  Summary: SDK for scientific agents
5
5
  Home-page: https://github.com/dptech-corp/bohr-agent-sdk/
6
6
  Author: DP Technology
@@ -9,7 +9,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
9
9
 
10
10
  setup(
11
11
  name="bohr-agent-sdk",
12
- version="0.1.111",
12
+ version="0.1.112",
13
13
  description="SDK for science agent and mcp tools",
14
14
  long_description=long_description,
15
15
  long_description_content_type="text/markdown",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bohr-agent-sdk
3
- Version: 0.1.111
3
+ Version: 0.1.112
4
4
  Summary: SDK for scientific agents
5
5
  Home-page: https://github.com/dptech-corp/bohr-agent-sdk/
6
6
  Author: DP Technology
@@ -7,17 +7,17 @@ from copy import deepcopy
7
7
  from datetime import datetime
8
8
  from pathlib import Path
9
9
  from urllib.parse import urlparse
10
- from typing import Any, Literal, Optional, TypedDict, List, Dict
10
+ from typing import Annotated, Literal, Optional, List, Dict
11
11
 
12
- import mcp
13
12
  from mcp.server.fastmcp import FastMCP
14
13
  from mcp.server.fastmcp.utilities.context_injection import (
15
14
  find_context_parameter,
16
15
  )
17
16
  from mcp.server.fastmcp.utilities.func_metadata import (
18
- _get_typed_signature,
17
+ ArgModelBase,
19
18
  func_metadata,
20
19
  )
20
+ from pydantic import BaseModel, Field, create_model
21
21
  from starlette.responses import JSONResponse
22
22
  from starlette.routing import Route
23
23
 
@@ -65,18 +65,9 @@ def set_directory(workdir: str):
65
65
  os.chdir(cwd)
66
66
 
67
67
 
68
- def load_executor(executor):
69
- if not executor and os.path.exists("executor.json"):
70
- with open("executor.json", "r") as f:
71
- executor = json.load(f)
72
- return executor
73
-
74
-
75
- def load_storage(storage):
76
- if not storage and os.path.exists("storage.json"):
77
- with open("storage.json", "r") as f:
78
- storage = json.load(f)
79
- return storage
68
+ def load_job_info():
69
+ with open("job.json", "r") as f:
70
+ return json.load(f)
80
71
 
81
72
 
82
73
  def query_job_status(job_id: str, executor: Optional[dict] = None
@@ -90,7 +81,7 @@ def query_job_status(job_id: str, executor: Optional[dict] = None
90
81
  """
91
82
  trace_id, exec_id = job_id.split("/")
92
83
  with set_directory(trace_id):
93
- executor = load_executor(executor)
84
+ executor = load_job_info()["executor"] or executor
94
85
  _, executor = init_executor(executor)
95
86
  status = executor.query_status(exec_id)
96
87
  logger.info("Job %s status is %s" % (job_id, status))
@@ -105,7 +96,7 @@ def terminate_job(job_id: str, executor: Optional[dict] = None):
105
96
  """
106
97
  trace_id, exec_id = job_id.split("/")
107
98
  with set_directory(trace_id):
108
- executor = load_executor(executor)
99
+ executor = load_job_info()["executor"] or executor
109
100
  _, executor = init_executor(executor)
110
101
  executor.terminate(exec_id)
111
102
  logger.info("Job %s is terminated" % job_id)
@@ -215,7 +206,8 @@ def handle_output_artifacts(results, exec_id, storage):
215
206
  "storage_type": storage_type,
216
207
  "uri": uri,
217
208
  }
218
- elif isinstance(results[name], list) and all(isinstance(item, Path) for item in results[name]):
209
+ elif isinstance(results[name], list) and all(
210
+ isinstance(item, Path) for item in results[name]):
219
211
  new_uris = []
220
212
  for item in results[name]:
221
213
  key = storage.upload("%s/outputs/%s" % (exec_id, name),
@@ -235,7 +227,7 @@ def handle_output_artifacts(results, exec_id, storage):
235
227
  # MCP does not regard Any as serializable in Python 3.12
236
228
  # use Optional[Any] to work around
237
229
  def get_job_results(job_id: str, executor: Optional[dict] = None,
238
- storage: Optional[dict] = None) -> Optional[Any]:
230
+ storage: Optional[dict] = None):
239
231
  """
240
232
  Get results of a calculation job
241
233
  Args:
@@ -245,8 +237,9 @@ def get_job_results(job_id: str, executor: Optional[dict] = None,
245
237
  """
246
238
  trace_id, exec_id = job_id.split("/")
247
239
  with set_directory(trace_id):
248
- executor = load_executor(executor)
249
- storage = load_storage(storage)
240
+ job_info = load_job_info()
241
+ executor = job_info["executor"] or executor
242
+ storage = job_info["storage"] or storage
250
243
  _, executor = init_executor(executor)
251
244
  results = executor.get_results(exec_id)
252
245
  results, output_artifacts = handle_output_artifacts(
@@ -254,7 +247,24 @@ def get_job_results(job_id: str, executor: Optional[dict] = None,
254
247
  logger.info("Job %s result is %s" % (job_id, results))
255
248
  return JobResult(result=results, job_info={
256
249
  "output_artifacts": output_artifacts,
257
- })
250
+ }, tool_name=job_info["tool_name"])
251
+
252
+
253
+ annotation_map = {
254
+ Path: str,
255
+ Optional[Path]: Optional[str],
256
+ List[Path]: List[str],
257
+ Optional[List[Path]]: Optional[List[str]],
258
+ Dict[str, Path]: Dict[str, str],
259
+ Optional[Dict[str, Path]]: Optional[Dict[str, str]],
260
+ Dict[str, List[Path]]: Dict[str, List[str]],
261
+ Optional[Dict[str, List[Path]]]: Optional[Dict[str, List[str]]],
262
+ }
263
+
264
+
265
+ class SubmitResult(BaseModel):
266
+ job_id: str
267
+ extra_info: dict | None = None
258
268
 
259
269
 
260
270
  class CalculationMCPServer:
@@ -268,71 +278,47 @@ class CalculationMCPServer:
268
278
  self.preprocess_func = preprocess_func
269
279
  self.fastmcp_mode = fastmcp_mode
270
280
  self.mcp = FastMCP(*args, **kwargs)
281
+ self.fn_metadata_map = {}
271
282
 
272
283
  def add_patched_tool(self, fn, new_fn, name, is_async=False, doc=None,
273
284
  override_return_annotation=False):
274
285
  """patch the metadata of the tool"""
275
286
  context_kwarg = find_context_parameter(fn)
276
-
277
- def _get_typed_signature_patched(call):
278
- """patch parameters"""
279
- typed_signature = _get_typed_signature(call)
280
- new_typed_signature = _get_typed_signature(new_fn)
281
- parameters = []
282
- for param in typed_signature.parameters.values():
283
- if param.annotation is Path:
284
- parameters.append(inspect.Parameter(
285
- name=param.name, default=param.default,
286
- annotation=str, kind=param.kind))
287
- elif param.annotation is Optional[Path]:
288
- parameters.append(inspect.Parameter(
289
- name=param.name, default=param.default,
290
- annotation=Optional[str], kind=param.kind))
291
- elif param.annotation is List[Path]:
292
- parameters.append(inspect.Parameter(
293
- name=param.name, default=param.default,
294
- annotation=List[str], kind=param.kind))
295
- elif param.annotation is Optional[List[Path]]:
296
- parameters.append(inspect.Parameter(
297
- name=param.name, default=param.default,
298
- annotation=Optional[List[str]], kind=param.kind))
299
- elif param.annotation is Dict[str, Path]:
300
- parameters.append(inspect.Parameter(
301
- name=param.name, default=param.default,
302
- annotation=Dict[str, str], kind=param.kind))
303
- elif param.annotation is Optional[Dict[str, Path]]:
304
- parameters.append(inspect.Parameter(
305
- name=param.name, default=param.default,
306
- annotation=Optional[Dict[str, str]], kind=param.kind))
307
- elif param.annotation is Dict[str, List[Path]]:
308
- parameters.append(inspect.Parameter(
309
- name=param.name, default=param.default,
310
- annotation=Dict[str, List[str]], kind=param.kind))
311
- elif param.annotation is Optional[Dict[str, List[Path]]]:
312
- parameters.append(inspect.Parameter(
313
- name=param.name, default=param.default,
314
- annotation=Optional[Dict[str, List[str]]], kind=param.kind))
315
- else:
316
- parameters.append(param)
317
- for param in new_typed_signature.parameters.values():
318
- if param.name != "kwargs":
319
- parameters.append(param)
320
- return inspect.Signature(
321
- parameters,
322
- return_annotation=(new_typed_signature.return_annotation
323
- if override_return_annotation
324
- else typed_signature.return_annotation))
325
-
326
- # Due to the frequent changes of MCP, we use a patching style here
327
- mcp.server.fastmcp.utilities.func_metadata._get_typed_signature = \
328
- _get_typed_signature_patched
329
287
  func_arg_metadata = func_metadata(
330
288
  fn,
331
289
  skip_names=[context_kwarg] if context_kwarg is not None else [],
332
- structured_output=None,
333
290
  )
334
- mcp.server.fastmcp.utilities.func_metadata._get_typed_signature = \
335
- _get_typed_signature
291
+ self.fn_metadata_map[name] = func_arg_metadata
292
+ model_params = {}
293
+ params = inspect.signature(fn, eval_str=True).parameters
294
+ for n, annotation in \
295
+ func_arg_metadata.arg_model.__annotations__.items():
296
+ param = params[n]
297
+ if param.annotation in annotation_map:
298
+ model_params[n] = Annotated[
299
+ (annotation_map[param.annotation], Field())]
300
+ else:
301
+ model_params[n] = annotation
302
+ if param.default is not inspect.Parameter.empty:
303
+ model_params[n] = (model_params[n], param.default)
304
+ for n, param in inspect.signature(new_fn).parameters.items():
305
+ if n == "kwargs":
306
+ continue
307
+ model_params[n] = Annotated[(param.annotation, Field())]
308
+ if param.default is not inspect.Parameter.empty:
309
+ model_params[n] = (model_params[n], param.default)
310
+
311
+ func_arg_metadata.arg_model = create_model(
312
+ f"{fn.__name__}Arguments",
313
+ __base__=ArgModelBase,
314
+ **model_params,
315
+ )
316
+ if override_return_annotation:
317
+ new_func_arg_metadata = func_metadata(new_fn)
318
+ func_arg_metadata.output_model = new_func_arg_metadata.output_model
319
+ func_arg_metadata.output_schema = \
320
+ new_func_arg_metadata.output_schema
321
+ func_arg_metadata.wrap_output = new_func_arg_metadata.wrap_output
336
322
  if self.fastmcp_mode and func_arg_metadata.wrap_output:
337
323
  # Only simulate behavior of fastmcp for output_schema
338
324
  func_arg_metadata.output_schema["x-fastmcp-wrap-result"] = True
@@ -341,16 +327,18 @@ class CalculationMCPServer:
341
327
  tool = Tool(
342
328
  fn=new_fn,
343
329
  name=name,
344
- description=doc or fn.__doc__,
330
+ description=doc or fn.__doc__ or "",
345
331
  parameters=parameters,
346
332
  fn_metadata=func_arg_metadata,
347
333
  is_async=is_async,
348
334
  context_kwarg=context_kwarg,
335
+ fn_metadata_map=self.fn_metadata_map,
349
336
  )
350
337
  self.mcp._tool_manager._tools[name] = tool
351
338
 
352
339
  def add_tool(self, fn, *args, **kwargs):
353
- tool = Tool.from_function(fn, *args, **kwargs)
340
+ tool = Tool.from_function(
341
+ fn, *args, fn_metadata_map=self.fn_metadata_map, **kwargs)
354
342
  self.mcp._tool_manager._tools[tool.name] = tool
355
343
  return tool
356
344
 
@@ -361,20 +349,20 @@ class CalculationMCPServer:
361
349
  def decorator(fn: Callable) -> Callable:
362
350
  def submit_job(executor: Optional[dict] = None,
363
351
  storage: Optional[dict] = None,
364
- **kwargs) -> TypedDict("results", {
365
- "job_id": str, "extra_info": Optional[dict]}):
352
+ **kwargs) -> SubmitResult:
366
353
  trace_id = datetime.today().strftime('%Y-%m-%d-%H:%M:%S.%f')
367
354
  logger.info("Job processing (Trace ID: %s)" % trace_id)
368
355
  with set_directory(trace_id):
369
356
  if preprocess_func is not None:
370
357
  executor, storage, kwargs = preprocess_func(
371
358
  executor, storage, kwargs)
372
- if executor:
373
- with open("executor.json", "w") as f:
374
- json.dump(executor, f, indent=4)
375
- if storage:
376
- with open("storage.json", "w") as f:
377
- json.dump(storage, f, indent=4)
359
+ job = {
360
+ "tool_name": fn.__name__,
361
+ "executor": executor,
362
+ "storage": storage,
363
+ }
364
+ with open("job.json", "w") as f:
365
+ json.dump(job, f, indent=4)
378
366
  kwargs, input_artifacts = handle_input_artifacts(
379
367
  fn, kwargs, storage)
380
368
  executor_type, executor = init_executor(executor)
@@ -382,10 +370,10 @@ class CalculationMCPServer:
382
370
  exec_id = res["job_id"]
383
371
  job_id = "%s/%s" % (trace_id, exec_id)
384
372
  logger.info("Job submitted (ID: %s)" % job_id)
385
- result = {
386
- "job_id": job_id,
387
- "extra_info": res.get("extra_info"),
388
- }
373
+ result = SubmitResult(
374
+ job_id=job_id,
375
+ extra_info=res.get("extra_info"),
376
+ )
389
377
  return JobResult(result=result, job_info={
390
378
  "trace_id": trace_id,
391
379
  "executor_type": executor_type,
@@ -21,6 +21,7 @@ def get_logger(name, level="INFO",
21
21
  class JobResult(BaseModel):
22
22
  result: Any
23
23
  job_info: dict
24
+ tool_name: str | None = None
24
25
 
25
26
 
26
27
  class Tool(mcp.server.fastmcp.tools.Tool):
@@ -28,13 +29,23 @@ class Tool(mcp.server.fastmcp.tools.Tool):
28
29
  Workaround MCP server cannot print traceback
29
30
  Add job info to first unstructured content
30
31
  """
32
+ fn_metadata_map: dict | None = None
33
+
34
+ @classmethod
35
+ def from_function(cls, *args, fn_metadata_map=None, **kwargs):
36
+ tool = super().from_function(*args, **kwargs)
37
+ tool.fn_metadata_map = fn_metadata_map
38
+ return tool
39
+
31
40
  async def run(self, *args, **kwargs):
32
41
  try:
33
42
  kwargs["convert_result"] = False
34
43
  result = await super().run(*args, **kwargs)
35
44
  if isinstance(result, JobResult):
36
45
  job_info = result.job_info
37
- result = self.fn_metadata.convert_result(result.result)
46
+ fn_metadata = self.fn_metadata_map.get(
47
+ result.tool_name, self.fn_metadata)
48
+ result = fn_metadata.convert_result(result.result)
38
49
  if isinstance(result, tuple) and len(result) == 2:
39
50
  unstructured_content, _ = result
40
51
  else: