mbxai 1.4.0__py3-none-any.whl → 1.6.0__py3-none-any.whl

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.
mbxai/__init__.py CHANGED
@@ -2,4 +2,4 @@
2
2
  MBX AI package.
3
3
  """
4
4
 
5
- __version__ = "1.4.0"
5
+ __version__ = "1.6.0"
mbxai/mcp/server.py CHANGED
@@ -31,7 +31,7 @@ class MCPServer:
31
31
  self.app = FastAPI(
32
32
  title=self.name,
33
33
  description=self.description,
34
- version="1.4.0",
34
+ version="1.6.0",
35
35
  )
36
36
 
37
37
  # Initialize MCP server
@@ -74,13 +74,10 @@ class MCPServer:
74
74
  tools = await self.mcp_server.list_tools()
75
75
  tool_metadata = tools[-1]
76
76
 
77
- # Convert FastMCP schema to our schema format
77
+ # Use the raw inputSchema from FastMCP - it will be processed later by convert_to_strict_schema
78
+ # when the tool is converted to OpenAI function format
78
79
  inputSchema = tool_metadata.inputSchema
79
- if isinstance(inputSchema, dict):
80
- if "$ref" in inputSchema:
81
- ref = inputSchema["$ref"].split("/")[-1]
82
- inputSchema = tool_metadata.inputSchema.get("$defs", {}).get(ref, {})
83
-
80
+
84
81
  # Create and store Tool instance
85
82
  self._tools[tool_metadata.name] = Tool(
86
83
  name=tool_metadata.name,
mbxai/tools/types.py CHANGED
@@ -9,16 +9,142 @@ import json
9
9
 
10
10
  logger = logging.getLogger(__name__)
11
11
 
12
+ def _resolve_ref(schema: dict[str, Any], ref: str, root_schema: dict[str, Any]) -> dict[str, Any]:
13
+ """Resolve a $ref to its actual schema definition.
14
+
15
+ Args:
16
+ schema: The current schema object
17
+ ref: The reference string (e.g., "#/$defs/MyType")
18
+ root_schema: The root schema containing $defs
19
+
20
+ Returns:
21
+ The resolved schema definition with all nested $refs also resolved
22
+ """
23
+ if ref.startswith("#/"):
24
+ # Remove the "#/" prefix and split the path
25
+ path_parts = ref[2:].split("/")
26
+ current = root_schema
27
+
28
+ # Navigate through the path
29
+ for part in path_parts:
30
+ if isinstance(current, dict) and part in current:
31
+ current = current[part]
32
+ else:
33
+ logger.warning(f"Could not resolve $ref: {ref}")
34
+ return {"type": "object", "additionalProperties": False}
35
+
36
+ if isinstance(current, dict):
37
+ # Recursively process the resolved schema to handle nested $refs
38
+ return _convert_property_to_strict(current, root_schema)
39
+ else:
40
+ return {"type": "object", "additionalProperties": False}
41
+ else:
42
+ logger.warning(f"Unsupported $ref format: {ref}")
43
+ return {"type": "object", "additionalProperties": False}
44
+
45
+ def _convert_property_to_strict(prop: dict[str, Any], root_schema: dict[str, Any]) -> dict[str, Any]:
46
+ """Convert a single property to strict format, recursively handling nested objects and arrays.
47
+
48
+ Args:
49
+ prop: The property definition to convert
50
+ root_schema: The root schema containing $defs
51
+
52
+ Returns:
53
+ A strict format property definition with all $refs resolved
54
+ """
55
+ # Handle $ref resolution first - this may return a completely different schema
56
+ if "$ref" in prop:
57
+ # Get the referenced schema path
58
+ ref = prop["$ref"]
59
+ if ref.startswith("#/"):
60
+ path_parts = ref[2:].split("/")
61
+ current = root_schema
62
+
63
+ # Navigate through the path to get the referenced schema
64
+ for part in path_parts:
65
+ if isinstance(current, dict) and part in current:
66
+ current = current[part]
67
+ else:
68
+ logger.warning(f"Could not resolve $ref: {ref}")
69
+ return {"type": "object", "additionalProperties": False}
70
+
71
+ if isinstance(current, dict):
72
+ # Recursively process the resolved schema
73
+ return _convert_property_to_strict(current, root_schema)
74
+ else:
75
+ return {"type": "object", "additionalProperties": False}
76
+ else:
77
+ logger.warning(f"Unsupported $ref format: {ref}")
78
+ return {"type": "object", "additionalProperties": False}
79
+
80
+ # Get the property type, defaulting to string
81
+ prop_type = prop.get("type", "string")
82
+
83
+ # Start with basic property structure
84
+ new_prop = {
85
+ "type": prop_type,
86
+ "description": prop.get("description", f"A {prop_type} parameter")
87
+ }
88
+
89
+ # Handle specific types
90
+ if prop_type == "object":
91
+ # Objects need additionalProperties: false and properties
92
+ new_prop["additionalProperties"] = False
93
+ new_prop["properties"] = {}
94
+ new_prop["required"] = []
95
+
96
+ # Process nested properties recursively
97
+ if "properties" in prop:
98
+ for nested_name, nested_prop in prop["properties"].items():
99
+ new_prop["properties"][nested_name] = _convert_property_to_strict(nested_prop, root_schema)
100
+
101
+ # Copy required fields
102
+ if "required" in prop:
103
+ new_prop["required"] = prop["required"]
104
+
105
+ elif prop_type == "array":
106
+ # Arrays must have items definition
107
+ if "items" in prop:
108
+ # Recursively process items schema (this could also have $refs)
109
+ new_prop["items"] = _convert_property_to_strict(prop["items"], root_schema)
110
+ else:
111
+ # Default items schema if missing
112
+ new_prop["items"] = {
113
+ "type": "string",
114
+ "description": "Array item"
115
+ }
116
+
117
+ elif prop_type in ["string", "number", "integer", "boolean"]:
118
+ # For primitive types, copy additional constraints
119
+ if "enum" in prop:
120
+ new_prop["enum"] = prop["enum"]
121
+ if "minimum" in prop:
122
+ new_prop["minimum"] = prop["minimum"]
123
+ if "maximum" in prop:
124
+ new_prop["maximum"] = prop["maximum"]
125
+ if "pattern" in prop:
126
+ new_prop["pattern"] = prop["pattern"]
127
+ if "format" in prop:
128
+ new_prop["format"] = prop["format"]
129
+
130
+ return new_prop
131
+
12
132
  def convert_to_strict_schema(schema: dict[str, Any], strict: bool = True, keep_input_wrapper: bool = False) -> dict[str, Any]:
13
133
  """Convert a schema to strict format required by OpenAI.
14
134
 
135
+ This function handles:
136
+ - Resolving all $ref and $defs to inline definitions
137
+ - Adding required 'items' property for arrays
138
+ - Ensuring all objects have 'additionalProperties: false'
139
+ - Recursively processing nested schemas
140
+
15
141
  Args:
16
142
  schema: The input schema to validate and convert
17
143
  strict: Whether to enforce strict validation with additionalProperties: false
18
144
  keep_input_wrapper: Whether to keep the input wrapper (for MCP tools)
19
145
 
20
146
  Returns:
21
- A schema in strict format
147
+ A schema in strict format with all references resolved
22
148
  """
23
149
  if not schema:
24
150
  return {"type": "object", "properties": {}, "required": [], "additionalProperties": False}
@@ -31,14 +157,16 @@ def convert_to_strict_schema(schema: dict[str, Any], strict: bool = True, keep_i
31
157
  "additionalProperties": False # Always enforce additionalProperties: false for OpenRouter
32
158
  }
33
159
 
160
+ # Store the root schema for $ref resolution
161
+ root_schema = schema
162
+
34
163
  # Handle input wrapper
35
164
  if "properties" in schema and "input" in schema["properties"]:
36
165
  inputSchema = schema["properties"]["input"]
37
166
 
38
167
  # If input has a $ref, resolve it
39
168
  if "$ref" in inputSchema:
40
- ref = inputSchema["$ref"].split("/")[-1]
41
- inputSchema = schema.get("$defs", {}).get(ref, {})
169
+ inputSchema = _resolve_ref(inputSchema, inputSchema["$ref"], root_schema)
42
170
 
43
171
  if keep_input_wrapper:
44
172
  # Create the input property schema
@@ -49,20 +177,10 @@ def convert_to_strict_schema(schema: dict[str, Any], strict: bool = True, keep_i
49
177
  "additionalProperties": False # Always enforce additionalProperties: false for OpenRouter
50
178
  }
51
179
 
52
- # Copy over input properties
180
+ # Process input properties
53
181
  if "properties" in inputSchema:
54
182
  for prop_name, prop in inputSchema["properties"].items():
55
- # Create a new property object with only allowed fields
56
- new_prop = {
57
- "type": prop.get("type", "string"),
58
- "description": prop.get("description", f"The {prop_name} parameter")
59
- }
60
-
61
- # If the property is an object, ensure it has additionalProperties: false
62
- if new_prop["type"] == "object":
63
- new_prop["additionalProperties"] = False
64
-
65
- input_prop_schema["properties"][prop_name] = new_prop
183
+ input_prop_schema["properties"][prop_name] = _convert_property_to_strict(prop, root_schema)
66
184
 
67
185
  # Copy over required fields for input schema
68
186
  if "required" in inputSchema:
@@ -78,17 +196,7 @@ def convert_to_strict_schema(schema: dict[str, Any], strict: bool = True, keep_i
78
196
  # If not keeping input wrapper, use input schema directly
79
197
  if "properties" in inputSchema:
80
198
  for prop_name, prop in inputSchema["properties"].items():
81
- # Create a new property object with only allowed fields
82
- new_prop = {
83
- "type": prop.get("type", "string"),
84
- "description": prop.get("description", f"The {prop_name} parameter")
85
- }
86
-
87
- # If the property is an object, ensure it has additionalProperties: false
88
- if new_prop["type"] == "object":
89
- new_prop["additionalProperties"] = False
90
-
91
- strict_schema["properties"][prop_name] = new_prop
199
+ strict_schema["properties"][prop_name] = _convert_property_to_strict(prop, root_schema)
92
200
 
93
201
  # Copy over required fields
94
202
  if "required" in inputSchema:
@@ -97,17 +205,7 @@ def convert_to_strict_schema(schema: dict[str, Any], strict: bool = True, keep_i
97
205
  # If no input wrapper, use the schema as is
98
206
  if "properties" in schema:
99
207
  for prop_name, prop in schema["properties"].items():
100
- # Create a new property object with only allowed fields
101
- new_prop = {
102
- "type": prop.get("type", "string"),
103
- "description": prop.get("description", f"The {prop_name} parameter")
104
- }
105
-
106
- # If the property is an object, ensure it has additionalProperties: false
107
- if new_prop["type"] == "object":
108
- new_prop["additionalProperties"] = False
109
-
110
- strict_schema["properties"][prop_name] = new_prop
208
+ strict_schema["properties"][prop_name] = _convert_property_to_strict(prop, root_schema)
111
209
 
112
210
  # Copy over required fields
113
211
  if "required" in schema:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mbxai
3
- Version: 1.4.0
3
+ Version: 1.6.0
4
4
  Summary: MBX AI SDK
5
5
  Project-URL: Homepage, https://www.mibexx.de
6
6
  Project-URL: Documentation, https://www.mibexx.de
@@ -1,4 +1,4 @@
1
- mbxai/__init__.py,sha256=E7p4_3-qHNUH7NcRh8unQZwHeS5pRX-O8zL-n0jaR2M,47
1
+ mbxai/__init__.py,sha256=k9FHAi4YDTkuiAHKU5ktijzvf4s2I3EFN1s7jqJPA0o,47
2
2
  mbxai/core.py,sha256=WMvmU9TTa7M_m-qWsUew4xH8Ul6xseCZ2iBCXJTW-Bs,196
3
3
  mbxai/examples/openrouter_example.py,sha256=-grXHKMmFLoh-yUIEMc31n8Gg1S7uSazBWCIOWxgbyQ,1317
4
4
  mbxai/examples/parse_example.py,sha256=eCKMJoOl6qwo8sDP6Trc6ncgjPlgTqi5tPE2kB5_P0k,3821
@@ -12,7 +12,7 @@ mbxai/examples/mcp/mcp_server_example.py,sha256=nFfg22Jnc6HMW_ezLO3So1xwDdx2_rIt
12
12
  mbxai/mcp/__init__.py,sha256=_ek9iYdYqW5saKetj4qDci11jxesQDiHPJRpHMKkxgU,175
13
13
  mbxai/mcp/client.py,sha256=QRzId6o4_WRWVv3rtm8cfZZGaoY_UlaOO-oqNjY-tmw,5219
14
14
  mbxai/mcp/example.py,sha256=oaol7AvvZnX86JWNz64KvPjab5gg1VjVN3G8eFSzuaE,2350
15
- mbxai/mcp/server.py,sha256=V-o8BugXj9Jj_yUIGepH0fCw8lrxYSpVRpI5CFfAvnk,3454
15
+ mbxai/mcp/server.py,sha256=tSc05RxhXpRdXSpu-noJmK0V48uru0v1JpcUr0P5fmA,3332
16
16
  mbxai/openrouter/__init__.py,sha256=Ito9Qp_B6q-RLGAQcYyTJVWwR2YAZvNqE-HIYXxhtD8,298
17
17
  mbxai/openrouter/client.py,sha256=3LD6WDJ8wjo_nefH5d1NJCsrWPvBc_KBf2NsItUoSt8,18302
18
18
  mbxai/openrouter/config.py,sha256=Ia93s-auim9Sq71eunVDbn9ET5xX2zusXpV4JBdHAzs,3251
@@ -21,8 +21,8 @@ mbxai/openrouter/schema.py,sha256=H_77ZrA9zmbX155bWpCJj1jehUyJPS0QybEW1IVAoe0,54
21
21
  mbxai/tools/__init__.py,sha256=ogxrHvgJ7OR62Lmd5x9Eh5d2C0jqWyQis7Zy3yKpZ78,218
22
22
  mbxai/tools/client.py,sha256=j6yB2hYxvWbaQ5SqN1Fs_YFdPtwettdcMoXcdeV-520,14930
23
23
  mbxai/tools/example.py,sha256=1HgKK39zzUuwFbnp3f0ThyWVfA_8P28PZcTwaUw5K78,2232
24
- mbxai/tools/types.py,sha256=7gNIJBjzr9i4DT50OGLMjn3-6yBXqlK-kIz_RWcqywo,5875
25
- mbxai-1.4.0.dist-info/METADATA,sha256=EWpGN5OMQTKiNodh1zxL-tU1Ud3U3-Q20Sz-Ep7YUuA,4147
26
- mbxai-1.4.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
27
- mbxai-1.4.0.dist-info/licenses/LICENSE,sha256=hEyhc4FxwYo3NQ40yNgZ7STqwVk-1_XcTXOnAPbGJAw,1069
28
- mbxai-1.4.0.dist-info/RECORD,,
24
+ mbxai/tools/types.py,sha256=OFfM7scDGTm4FOcJA2ecj-fxL1MEBkqPsT3hqCL1Jto,9505
25
+ mbxai-1.6.0.dist-info/METADATA,sha256=8xzN7T2h5j8XyFMaq2CpXi3zMoe7tyM1RvjzYCpJz6U,4147
26
+ mbxai-1.6.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
27
+ mbxai-1.6.0.dist-info/licenses/LICENSE,sha256=hEyhc4FxwYo3NQ40yNgZ7STqwVk-1_XcTXOnAPbGJAw,1069
28
+ mbxai-1.6.0.dist-info/RECORD,,
File without changes