universal-mcp 0.1.20rc1__tar.gz → 0.1.21rc2__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 (55) hide show
  1. {universal_mcp-0.1.20rc1 → universal_mcp-0.1.21rc2}/PKG-INFO +1 -1
  2. {universal_mcp-0.1.20rc1 → universal_mcp-0.1.21rc2}/pyproject.toml +1 -1
  3. {universal_mcp-0.1.20rc1 → universal_mcp-0.1.21rc2}/src/tests/test_tool.py +7 -8
  4. {universal_mcp-0.1.20rc1 → universal_mcp-0.1.21rc2}/src/universal_mcp/servers/server.py +2 -1
  5. {universal_mcp-0.1.20rc1 → universal_mcp-0.1.21rc2}/src/universal_mcp/tools/adapters.py +1 -1
  6. {universal_mcp-0.1.20rc1 → universal_mcp-0.1.21rc2}/src/universal_mcp/tools/func_metadata.py +4 -2
  7. {universal_mcp-0.1.20rc1 → universal_mcp-0.1.21rc2}/src/universal_mcp/utils/openapi/openapi.py +133 -50
  8. {universal_mcp-0.1.20rc1 → universal_mcp-0.1.21rc2}/.gitignore +0 -0
  9. {universal_mcp-0.1.20rc1 → universal_mcp-0.1.21rc2}/LICENSE +0 -0
  10. {universal_mcp-0.1.20rc1 → universal_mcp-0.1.21rc2}/README.md +0 -0
  11. {universal_mcp-0.1.20rc1 → universal_mcp-0.1.21rc2}/src/tests/__init__.py +0 -0
  12. {universal_mcp-0.1.20rc1 → universal_mcp-0.1.21rc2}/src/tests/conftest.py +0 -0
  13. {universal_mcp-0.1.20rc1 → universal_mcp-0.1.21rc2}/src/tests/test_api_generator.py +0 -0
  14. {universal_mcp-0.1.20rc1 → universal_mcp-0.1.21rc2}/src/tests/test_api_integration.py +0 -0
  15. {universal_mcp-0.1.20rc1 → universal_mcp-0.1.21rc2}/src/tests/test_applications.py +0 -0
  16. {universal_mcp-0.1.20rc1 → universal_mcp-0.1.21rc2}/src/tests/test_localserver.py +0 -0
  17. {universal_mcp-0.1.20rc1 → universal_mcp-0.1.21rc2}/src/tests/test_stores.py +0 -0
  18. {universal_mcp-0.1.20rc1 → universal_mcp-0.1.21rc2}/src/tests/test_tool_manager.py +0 -0
  19. {universal_mcp-0.1.20rc1 → universal_mcp-0.1.21rc2}/src/tests/test_zenquotes.py +0 -0
  20. {universal_mcp-0.1.20rc1 → universal_mcp-0.1.21rc2}/src/universal_mcp/__init__.py +0 -0
  21. {universal_mcp-0.1.20rc1 → universal_mcp-0.1.21rc2}/src/universal_mcp/analytics.py +0 -0
  22. {universal_mcp-0.1.20rc1 → universal_mcp-0.1.21rc2}/src/universal_mcp/applications/README.md +0 -0
  23. {universal_mcp-0.1.20rc1 → universal_mcp-0.1.21rc2}/src/universal_mcp/applications/__init__.py +0 -0
  24. {universal_mcp-0.1.20rc1 → universal_mcp-0.1.21rc2}/src/universal_mcp/applications/application.py +0 -0
  25. {universal_mcp-0.1.20rc1 → universal_mcp-0.1.21rc2}/src/universal_mcp/cli.py +0 -0
  26. {universal_mcp-0.1.20rc1 → universal_mcp-0.1.21rc2}/src/universal_mcp/config.py +0 -0
  27. {universal_mcp-0.1.20rc1 → universal_mcp-0.1.21rc2}/src/universal_mcp/exceptions.py +0 -0
  28. {universal_mcp-0.1.20rc1 → universal_mcp-0.1.21rc2}/src/universal_mcp/integrations/README.md +0 -0
  29. {universal_mcp-0.1.20rc1 → universal_mcp-0.1.21rc2}/src/universal_mcp/integrations/__init__.py +0 -0
  30. {universal_mcp-0.1.20rc1 → universal_mcp-0.1.21rc2}/src/universal_mcp/integrations/integration.py +0 -0
  31. {universal_mcp-0.1.20rc1 → universal_mcp-0.1.21rc2}/src/universal_mcp/logger.py +0 -0
  32. {universal_mcp-0.1.20rc1 → universal_mcp-0.1.21rc2}/src/universal_mcp/py.typed +0 -0
  33. {universal_mcp-0.1.20rc1 → universal_mcp-0.1.21rc2}/src/universal_mcp/servers/README.md +0 -0
  34. {universal_mcp-0.1.20rc1 → universal_mcp-0.1.21rc2}/src/universal_mcp/servers/__init__.py +0 -0
  35. {universal_mcp-0.1.20rc1 → universal_mcp-0.1.21rc2}/src/universal_mcp/stores/README.md +0 -0
  36. {universal_mcp-0.1.20rc1 → universal_mcp-0.1.21rc2}/src/universal_mcp/stores/__init__.py +0 -0
  37. {universal_mcp-0.1.20rc1 → universal_mcp-0.1.21rc2}/src/universal_mcp/stores/store.py +0 -0
  38. {universal_mcp-0.1.20rc1 → universal_mcp-0.1.21rc2}/src/universal_mcp/tools/README.md +0 -0
  39. {universal_mcp-0.1.20rc1 → universal_mcp-0.1.21rc2}/src/universal_mcp/tools/__init__.py +0 -0
  40. {universal_mcp-0.1.20rc1 → universal_mcp-0.1.21rc2}/src/universal_mcp/tools/manager.py +0 -0
  41. {universal_mcp-0.1.20rc1 → universal_mcp-0.1.21rc2}/src/universal_mcp/tools/tools.py +0 -0
  42. {universal_mcp-0.1.20rc1 → universal_mcp-0.1.21rc2}/src/universal_mcp/utils/__init__.py +0 -0
  43. {universal_mcp-0.1.20rc1 → universal_mcp-0.1.21rc2}/src/universal_mcp/utils/agentr.py +0 -0
  44. {universal_mcp-0.1.20rc1 → universal_mcp-0.1.21rc2}/src/universal_mcp/utils/common.py +0 -0
  45. {universal_mcp-0.1.20rc1 → universal_mcp-0.1.21rc2}/src/universal_mcp/utils/docstring_parser.py +0 -0
  46. {universal_mcp-0.1.20rc1 → universal_mcp-0.1.21rc2}/src/universal_mcp/utils/installation.py +0 -0
  47. {universal_mcp-0.1.20rc1 → universal_mcp-0.1.21rc2}/src/universal_mcp/utils/openapi/__inti__.py +0 -0
  48. {universal_mcp-0.1.20rc1 → universal_mcp-0.1.21rc2}/src/universal_mcp/utils/openapi/api_generator.py +0 -0
  49. {universal_mcp-0.1.20rc1 → universal_mcp-0.1.21rc2}/src/universal_mcp/utils/openapi/api_splitter.py +0 -0
  50. {universal_mcp-0.1.20rc1 → universal_mcp-0.1.21rc2}/src/universal_mcp/utils/openapi/docgen.py +0 -0
  51. {universal_mcp-0.1.20rc1 → universal_mcp-0.1.21rc2}/src/universal_mcp/utils/openapi/preprocessor.py +0 -0
  52. {universal_mcp-0.1.20rc1 → universal_mcp-0.1.21rc2}/src/universal_mcp/utils/openapi/readme.py +0 -0
  53. {universal_mcp-0.1.20rc1 → universal_mcp-0.1.21rc2}/src/universal_mcp/utils/singleton.py +0 -0
  54. {universal_mcp-0.1.20rc1 → universal_mcp-0.1.21rc2}/src/universal_mcp/utils/templates/README.md.j2 +0 -0
  55. {universal_mcp-0.1.20rc1 → universal_mcp-0.1.21rc2}/src/universal_mcp/utils/templates/api_client.py.j2 +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: universal-mcp
3
- Version: 0.1.20rc1
3
+ Version: 0.1.21rc2
4
4
  Summary: Universal MCP acts as a middle ware for your API applications. It can store your credentials, authorize, enable disable apps on the fly and much more.
5
5
  Author-email: Manoj Bajaj <manojbajaj95@gmail.com>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "universal-mcp"
7
- version = "0.1.20-rc1"
7
+ version = "0.1.21-rc2"
8
8
  description = "Universal MCP acts as a middle ware for your API applications. It can store your credentials, authorize, enable disable apps on the fly and much more."
9
9
  readme = "README.md"
10
10
  authors = [
@@ -18,7 +18,7 @@ def test_func_metadata_annotated():
18
18
  "title": "funcArguments",
19
19
  "properties": {
20
20
  "a": {"type": "integer", "title": "First integer"},
21
- "b": {"type": "integer", "title": "B"},
21
+ "b": {"type": "integer", "title": "b"},
22
22
  },
23
23
  "required": ["a", "b"],
24
24
  }
@@ -40,8 +40,8 @@ def test_func_metadata_no_annotated():
40
40
  "type": "object",
41
41
  "title": "funcArguments",
42
42
  "properties": {
43
- "a": {"type": "integer", "title": "The first integer"},
44
- "b": {"type": "integer", "title": "The second integer"},
43
+ "a": {"type": "integer", "description": "The first integer", "title": "a"},
44
+ "b": {"type": "integer", "description": "The second integer", "title": "b"},
45
45
  },
46
46
  "required": ["a", "b"],
47
47
  }
@@ -57,7 +57,6 @@ def test_func_metadata_no_args():
57
57
  "type": "object",
58
58
  "title": "funcArguments",
59
59
  "properties": {},
60
- # "required": [],
61
60
  }
62
61
 
63
62
 
@@ -88,9 +87,9 @@ def test_func_metadata_required():
88
87
  "type": "object",
89
88
  "title": "funcArguments",
90
89
  "properties": {
91
- "a": {"type": "integer", "title": "A"},
92
- "b": {"type": "string", "title": "B"},
93
- "c": {"type": "number", "title": "C", "default": 1.0},
90
+ "a": {"type": "integer", "title": "a"},
91
+ "b": {"type": "string", "title": "b"},
92
+ "c": {"type": "number", "title": "c", "default": 1.0},
94
93
  },
95
94
  "required": ["a", "b"],
96
95
  }
@@ -106,7 +105,7 @@ def test_func_metadata_none_type():
106
105
  "type": "object",
107
106
  "title": "funcArguments",
108
107
  "properties": {
109
- "a": {"type": "null", "title": "A", "default": None},
108
+ "a": {"type": "null", "title": "a", "default": None},
110
109
  },
111
110
  }
112
111
 
@@ -13,6 +13,7 @@ from universal_mcp.exceptions import ConfigurationError, ToolError
13
13
  from universal_mcp.integrations import AgentRIntegration, integration_from_config
14
14
  from universal_mcp.stores import BaseStore, store_from_config
15
15
  from universal_mcp.tools import ToolManager
16
+ from universal_mcp.tools.adapters import ToolFormat
16
17
  from universal_mcp.utils.agentr import AgentrClient
17
18
 
18
19
 
@@ -56,7 +57,7 @@ class BaseServer(FastMCP):
56
57
  Returns:
57
58
  List of tool definitions
58
59
  """
59
- return self._tool_manager.list_tools(format="mcp")
60
+ return self._tool_manager.list_tools(format=ToolFormat.MCP)
60
61
 
61
62
  def _format_tool_result(self, result: Any) -> list[TextContent]:
62
63
  """Format tool result into TextContent list.
@@ -17,7 +17,7 @@ def convert_tool_to_mcp_tool(
17
17
  from mcp.server.fastmcp.server import MCPTool
18
18
 
19
19
  return MCPTool(
20
- name=tool.name,
20
+ name=tool.name[:63],
21
21
  description=tool.description or "",
22
22
  inputSchema=tool.parameters,
23
23
  )
@@ -192,8 +192,10 @@ class FuncMetadata(BaseModel):
192
192
  _get_typed_annotation(annotation, globalns),
193
193
  param.default if param.default is not inspect.Parameter.empty else PydanticUndefined,
194
194
  )
195
- if not field_info.title and arg_description and arg_description.get(param.name):
196
- field_info.title = arg_description.get(param.name)
195
+ if not field_info.title:
196
+ field_info.title = param.name
197
+ if not field_info.description and arg_description and arg_description.get(param.name):
198
+ field_info.description = arg_description.get(param.name)
197
199
  dynamic_pydantic_model_params[param.name] = (
198
200
  field_info.annotation,
199
201
  field_info,
@@ -19,6 +19,7 @@ class Parameters(BaseModel):
19
19
  required: bool
20
20
  example: str | None = None
21
21
  is_file: bool = False
22
+ schema: dict = {}
22
23
 
23
24
  def __str__(self):
24
25
  return f"{self.name}: ({self.type})"
@@ -79,6 +80,61 @@ def convert_to_snake_case(identifier: str) -> str:
79
80
  return result.strip("_").lower()
80
81
 
81
82
 
83
+ # Added new recursive type mapper
84
+ def _openapi_type_to_python_type(schema: dict, required: bool = True) -> str:
85
+ """
86
+ Recursively map OpenAPI schema to Python type hints.
87
+ """
88
+ openapi_type = schema.get("type")
89
+
90
+
91
+ if "$ref" in schema and not openapi_type:
92
+ py_type = "dict[str, Any]"
93
+ elif openapi_type == "array":
94
+ items_schema = schema.get("items", {})
95
+ item_type = _openapi_type_to_python_type(items_schema, required=True)
96
+ py_type = f"List[{item_type}]"
97
+ elif openapi_type == "object":
98
+
99
+ if schema.get("format") in ["binary", "byte"]:
100
+ py_type = "bytes"
101
+ else:
102
+
103
+ if "additionalProperties" in schema and isinstance(schema["additionalProperties"], dict):
104
+ additional_props_schema = schema["additionalProperties"]
105
+
106
+ value_type = _openapi_type_to_python_type(additional_props_schema, required=True)
107
+ py_type = f"dict[str, {value_type}]"
108
+ elif not schema.get("properties") and not schema.get("allOf") and not schema.get("oneOf") and not schema.get("anyOf"):
109
+
110
+ py_type = "dict[str, Any]"
111
+ else:
112
+
113
+ py_type = "dict[str, Any]"
114
+ elif openapi_type == "integer":
115
+ py_type = "int"
116
+ elif openapi_type == "number":
117
+ py_type = "float"
118
+ elif openapi_type == "boolean":
119
+ py_type = "bool"
120
+ elif openapi_type == "string":
121
+ if schema.get("format") in ["binary", "byte"]:
122
+ py_type = "bytes"
123
+ elif schema.get("format") == "date" or schema.get("format") == "date-time":
124
+ py_type = "str"
125
+ else:
126
+ py_type = "str"
127
+ else:
128
+
129
+ py_type = "Any"
130
+
131
+ if not required:
132
+ if py_type.startswith("Optional[") and py_type.endswith("]"):
133
+ return py_type
134
+ return f"Optional[{py_type}]"
135
+ return py_type
136
+
137
+
82
138
  def _sanitize_identifier(name: str | None) -> str:
83
139
  """Cleans a string to be a valid Python identifier.
84
140
 
@@ -235,6 +291,7 @@ def _generate_path_params(path: str) -> list[Parameters]:
235
291
  where="path",
236
292
  required=True,
237
293
  example=None,
294
+ schema={"type": "string"}
238
295
  )
239
296
  )
240
297
  except Exception as e:
@@ -283,6 +340,7 @@ def _generate_query_params(operation: dict[str, Any]) -> list[Parameters]:
283
340
  where=where,
284
341
  required=required,
285
342
  example=str(example_value) if example_value is not None else None,
343
+ schema=param_schema if param_schema else {"type": type_value}
286
344
  )
287
345
  query_params.append(parameter)
288
346
  return query_params
@@ -318,7 +376,8 @@ def _generate_body_params(schema_to_process: dict[str, Any] | None, overall_body
318
376
  where="body",
319
377
  required=param_required,
320
378
  example=str(param_example) if param_example is not None else None,
321
- is_file=current_is_file
379
+ is_file=current_is_file,
380
+ schema=param_schema_details
322
381
  )
323
382
  )
324
383
  # print(f"[DEBUG] Final body_params list generated: {body_params}") # DEBUG
@@ -497,21 +556,34 @@ def _generate_method_code(path, method, operation):
497
556
  required_args = []
498
557
  optional_args = []
499
558
 
559
+ # Arguments for the function signature with type hints
560
+ signature_required_args_typed = []
561
+ signature_optional_args_typed = []
562
+
500
563
  # Process Path Parameters (Highest Priority)
501
564
  for param in path_params:
502
565
  # Path param names are sanitized but not suffixed by aliasing.
503
566
  if param.name not in required_args: # param.name is the sanitized name
504
567
  required_args.append(param.name)
568
+ # For signature with types
569
+ param_py_type = _openapi_type_to_python_type(param.schema, required=True)
570
+ signature_required_args_typed.append(f"{param.name}: {param_py_type}")
505
571
 
506
572
  # Process Query Parameters
507
573
  for param in query_params: # param.name is the potentially aliased name (e.g., id_query)
508
574
  arg_name_for_sig = param.name
509
575
  current_arg_names_set = set(required_args) | {arg.split("=")[0] for arg in optional_args}
576
+
577
+ # For signature with types
578
+ param_py_type = _openapi_type_to_python_type(param.schema, required=param.required)
579
+
510
580
  if arg_name_for_sig not in current_arg_names_set:
511
581
  if param.required:
512
582
  required_args.append(arg_name_for_sig)
583
+ signature_required_args_typed.append(f"{arg_name_for_sig}: {param_py_type}")
513
584
  else:
514
585
  optional_args.append(f"{arg_name_for_sig}=None")
586
+ signature_optional_args_typed.append(f"{arg_name_for_sig}: {param_py_type} = None")
515
587
 
516
588
  # Process Body Parameters / Request Body
517
589
  # This list tracks the *final* names of parameters in the signature that come from the request body,
@@ -539,10 +611,16 @@ def _generate_method_code(path, method, operation):
539
611
  final_array_param_name = f"{array_param_name_base}_body_{counter}"
540
612
  counter += 1
541
613
 
614
+ # For signature with types
615
+ # The schema for an array body is body_schema_to_use itself
616
+ array_body_py_type = _openapi_type_to_python_type(body_schema_to_use, required=body_required)
617
+
542
618
  if body_required:
543
619
  required_args.append(final_array_param_name)
620
+ signature_required_args_typed.append(f"{final_array_param_name}: {array_body_py_type}")
544
621
  else:
545
622
  optional_args.append(f"{final_array_param_name}=None")
623
+ signature_optional_args_typed.append(f"{final_array_param_name}: {array_body_py_type} = None")
546
624
  final_request_body_arg_names_for_signature.append(final_array_param_name)
547
625
 
548
626
  # New: Handle raw body parameter (if body_params is empty but body is expected and not array/empty JSON)
@@ -563,10 +641,22 @@ def _generate_method_code(path, method, operation):
563
641
  counter += 1
564
642
  raw_body_param_name = temp_raw_body_name
565
643
 
644
+ # For signature with types
645
+ # Determine type based on selected_content_type for raw body
646
+ raw_body_schema_for_type = {"type": "string", "format": "binary"} # Default to bytes
647
+ if selected_content_type and "text" in selected_content_type:
648
+ raw_body_schema_for_type = {"type": "string"}
649
+ elif selected_content_type and selected_content_type.startswith("image/"):
650
+ raw_body_schema_for_type = {"type": "string", "format": "binary"} # image is bytes
651
+
652
+ raw_body_py_type = _openapi_type_to_python_type(raw_body_schema_for_type, required=body_required)
653
+
566
654
  if body_required: # If the raw body itself is required
567
655
  required_args.append(raw_body_param_name)
656
+ signature_required_args_typed.append(f"{raw_body_param_name}: {raw_body_py_type}")
568
657
  else:
569
658
  optional_args.append(f"{raw_body_param_name}=None")
659
+ signature_optional_args_typed.append(f"{raw_body_param_name}: {raw_body_py_type} = None")
570
660
  final_request_body_arg_names_for_signature.append(raw_body_param_name)
571
661
 
572
662
  elif body_params: # Object body with discernible properties
@@ -575,12 +665,18 @@ def _generate_method_code(path, method, operation):
575
665
 
576
666
  # Defensive check against already added args
577
667
  current_arg_names_set_loop = set(required_args) | {arg.split("=")[0] for arg in optional_args}
668
+
669
+ # For signature with types
670
+ param_py_type = _openapi_type_to_python_type(param.schema, required=param.required)
671
+
578
672
  if arg_name_for_sig not in current_arg_names_set_loop:
579
673
  if param.required:
580
674
  required_args.append(arg_name_for_sig)
675
+ signature_required_args_typed.append(f"{arg_name_for_sig}: {param_py_type}")
581
676
  else:
582
677
  # Parameters model does not store schema 'default'. Optional params default to None.
583
678
  optional_args.append(f"{arg_name_for_sig}=None")
679
+ signature_optional_args_typed.append(f"{arg_name_for_sig}: {param_py_type} = None")
584
680
  final_request_body_arg_names_for_signature.append(arg_name_for_sig)
585
681
 
586
682
 
@@ -599,17 +695,26 @@ def _generate_method_code(path, method, operation):
599
695
  final_empty_body_param_name = f"{empty_body_param_name_base}_body_{counter}"
600
696
  counter += 1
601
697
 
602
- # Check if it was somehow added by other logic (e.g. if 'request_body' was an explicit param name)
698
+ # For signature with types
699
+ # Empty body usually implies an empty JSON object, so dict or Optional[dict]
700
+ empty_body_py_type = _openapi_type_to_python_type({"type": "object"}, required=False)
603
701
 
604
702
  if final_empty_body_param_name not in (set(required_args) | {arg.split("=")[0] for arg in optional_args}):
605
703
  optional_args.append(f"{final_empty_body_param_name}=None")
704
+ # Add to typed signature list
705
+ signature_optional_args_typed.append(f"{final_empty_body_param_name}: {empty_body_py_type} = None")
706
+
606
707
  # Track for docstring, even if it's just 'request_body' or 'request_body_body'
607
708
  if final_empty_body_param_name not in final_request_body_arg_names_for_signature:
608
709
  final_request_body_arg_names_for_signature.append(final_empty_body_param_name)
609
710
 
610
- # Combine required and optional arguments
711
+ # Combine required and optional arguments FOR DOCSTRING (as before, without types)
611
712
  args = required_args + optional_args
612
- print(f"[DEBUG] Final combined args for signature: {args}") # DEBUG
713
+ print(f"[DEBUG] Final combined args for DOCSTRING: {args}") # DEBUG
714
+
715
+ # Combine required and optional arguments FOR SIGNATURE (with types)
716
+ signature_args_combined_typed = signature_required_args_typed + signature_optional_args_typed
717
+ print(f"[DEBUG] Final combined args for SIGNATURE: {signature_args_combined_typed}") # DEBUG
613
718
 
614
719
  # ----- Build Docstring -----
615
720
  # This section constructs the entire docstring for the generated method,
@@ -643,43 +748,28 @@ def _generate_method_code(path, method, operation):
643
748
  param_details[param_obj.name] = param_obj
644
749
 
645
750
  # Fetch request body example
646
- request_body_example_str = None
751
+ example_data = None # Initialize example_data here for wider scope
752
+
647
753
  if has_body:
648
754
  try:
649
755
  json_content = operation["requestBody"]["content"]["application/json"]
650
- example_data = None
756
+ #From direct content definition
651
757
  if "example" in json_content:
652
758
  example_data = json_content["example"]
653
759
  elif "examples" in json_content and json_content["examples"]:
654
760
  first_example_key = list(json_content["examples"].keys())[0]
655
761
  example_data = json_content["examples"][first_example_key].get("value")
656
-
657
- if example_data is not None:
658
- try:
659
- example_json = json.dumps(example_data, indent=2)
660
- indented_example = textwrap.indent(example_json, " " * 8) # 8 spaces
661
- request_body_example_str = f"\n Example:\n ```json\n{indented_example}\n ```"
662
- except TypeError:
663
- request_body_example_str = f"\n Example: {example_data}"
762
+ #If not found directly, try from resolved body schema (for nested/referenced examples)
763
+ if example_data is None and body_schema_to_use and "example" in body_schema_to_use:
764
+ example_data = body_schema_to_use["example"]
664
765
  except KeyError:
665
- pass # No example found
666
-
667
- # Identify the last argument related to the request body
668
- last_body_arg_name = None
669
- # request_body_params contains the names as they appear in the signature
670
- if final_request_body_arg_names_for_signature: # Use the new list with final aliased names
671
- # Find which of these appears last in the combined args list
672
- body_args_in_signature = [
673
- a.split("=")[0] for a in args if a.split("=")[0] in final_request_body_arg_names_for_signature
674
- ]
675
- if body_args_in_signature:
676
- last_body_arg_name = body_args_in_signature[-1]
766
+ pass # No example found or application/json content not present
677
767
 
678
768
  if signature_arg_names:
679
769
  args_doc_lines.append("Args:")
680
770
  for arg_signature_str in args:
681
771
  arg_name = arg_signature_str.split("=")[0]
682
- example_str = None # Initialize example_str here
772
+ example_str = None # Initialize example_str here for each argument
683
773
  detail = param_details.get(arg_name)
684
774
  if detail:
685
775
  desc = detail.description or "No description provided."
@@ -692,26 +782,22 @@ def _generate_method_code(path, method, operation):
692
782
  if detail.example and not detail.is_file: # Don't show schema example for file inputs
693
783
  example_str = repr(detail.example)
694
784
  arg_line += f" Example: {example_str}."
695
-
696
- # Append the full body example after the last body-related argument
697
- if arg_name == last_body_arg_name and request_body_example_str:
698
- # Remove the simple Example: if it exists before adding the detailed one
699
- if example_str and (
700
- f" Example: {example_str}." in arg_line or f" Example: {example_str} ." in arg_line
701
- ):
702
- arg_line = arg_line.replace(
703
- f" Example: {example_str}.", ""
704
- ) # Remove with or without trailing period
705
- arg_line += request_body_example_str # Append the formatted JSON example
785
+ # Fallback for body parameters if no direct example was found
786
+ elif not example_str and detail.where == "body" and example_data and isinstance(example_data, dict) and detail.identifier in example_data:
787
+ current_body_param_example = example_data[detail.identifier]
788
+ if current_body_param_example is not None: # Ensure the extracted part is not None
789
+ try:
790
+ arg_line += f" Example: {repr(current_body_param_example)}."
791
+ except Exception: # Fallback if repr fails
792
+ arg_line += " Example: [Could not represent example]."
706
793
 
707
794
  args_doc_lines.append(arg_line)
708
- elif arg_name == final_empty_body_param_name and has_empty_body: # Use potentially suffixed name
795
+
796
+ elif arg_name == final_empty_body_param_name and has_empty_body:
709
797
  args_doc_lines.append(
710
798
  f" {arg_name} (dict | None): Optional dictionary for an empty JSON request body (e.g., {{}})."
711
799
  )
712
- if ( arg_name == last_body_arg_name and request_body_example_str ):
713
- args_doc_lines[-1] += request_body_example_str
714
- elif arg_name == raw_body_param_name: # Docstring for raw body parameter
800
+ elif arg_name == raw_body_param_name:
715
801
  raw_body_type_hint = "bytes"
716
802
  raw_body_desc = "Raw binary content for the request body."
717
803
  if selected_content_type and "text" in selected_content_type:
@@ -720,13 +806,9 @@ def _generate_method_code(path, method, operation):
720
806
  elif selected_content_type and selected_content_type.startswith("image/"):
721
807
  raw_body_type_hint = "bytes (image data)"
722
808
  raw_body_desc = f"Raw image content ({selected_content_type}) for the request body."
723
-
724
809
  args_doc_lines.append(
725
810
  f" {arg_name} ({raw_body_type_hint} | None): {raw_body_desc}"
726
811
  )
727
- # Example for raw body is harder to give generically, but if present in spec, could be added.
728
- if ( arg_name == last_body_arg_name and request_body_example_str ):
729
- args_doc_lines[-1] += request_body_example_str
730
812
 
731
813
  if args_doc_lines:
732
814
  docstring_parts.append("\n".join(args_doc_lines))
@@ -765,8 +847,9 @@ def _generate_method_code(path, method, operation):
765
847
  # ----- End Build Docstring -----
766
848
 
767
849
  # --- Construct Method Signature String ---
768
- if args:
769
- signature = f" def {func_name}(self, {', '.join(args)}) -> {return_type}:"
850
+ # Use signature_args_combined_typed for the signature
851
+ if signature_args_combined_typed:
852
+ signature = f" def {func_name}(self, {', '.join(signature_args_combined_typed)}) -> {return_type}:"
770
853
  else:
771
854
  signature = f" def {func_name}(self) -> {return_type}:"
772
855
 
@@ -988,7 +1071,7 @@ def generate_api_client(schema, class_name: str | None = None):
988
1071
 
989
1072
  # Generate class imports
990
1073
  imports = [
991
- "from typing import Any",
1074
+ "from typing import Any, Optional, List",
992
1075
  "from universal_mcp.applications import APIApplication",
993
1076
  "from universal_mcp.integrations import Integration",
994
1077
  ]
@@ -1062,4 +1145,4 @@ if __name__ == "__main__":
1062
1145
 
1063
1146
  schema = load_schema("openapi.yaml")
1064
1147
  code = generate_api_client(schema)
1065
- print(code)
1148
+ print(code)