mbxai 0.6.18__py3-none-any.whl → 0.6.20__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__ = "0.5.25"
5
+ __version__ = "0.6.20"
mbxai/mcp/client.py CHANGED
@@ -7,7 +7,7 @@ import asyncio
7
7
  import json
8
8
  from pydantic import BaseModel, Field
9
9
 
10
- from ..tools import ToolClient, Tool
10
+ from ..tools import ToolClient, Tool, convert_to_strict_schema
11
11
  from ..openrouter import OpenRouterClient
12
12
 
13
13
  logger = logging.getLogger(__name__)
@@ -16,22 +16,15 @@ T = TypeVar("T", bound=BaseModel)
16
16
 
17
17
 
18
18
  class MCPTool(Tool):
19
- """MCP tool definition."""
20
- internal_url: str | None = Field(default=None, description="The internal URL to invoke the tool")
21
- service: str = Field(description="The service that provides the tool")
22
- strict: bool = Field(default=True, description="Whether the tool response is strictly validated")
23
- input_schema: dict[str, Any] = Field(description="The input schema for the tool")
24
- function: Callable[..., Any] | None = Field(default=None, description="The function that implements the tool")
19
+ """A tool from the MCP server."""
20
+ input_schema: dict[str, Any]
21
+ internal_url: str
22
+ strict: bool = True
25
23
 
26
24
  def to_openai_function(self) -> dict[str, Any]:
27
25
  """Convert the tool to an OpenAI function definition."""
28
- # Log the original schema
29
- logger.info(f"Original schema for {self.name}: {json.dumps(self.input_schema, indent=2)}")
30
-
31
- # Convert the schema to strict format
32
- strict_schema = self._convert_to_openai_schema(self.input_schema)
33
-
34
- # Log the converted schema
26
+ # Use the base Tool's schema conversion
27
+ strict_schema = convert_to_strict_schema(self.input_schema, strict=self.strict)
35
28
  logger.info(f"Converted schema for {self.name}: {json.dumps(strict_schema, indent=2)}")
36
29
 
37
30
  return {
@@ -48,61 +41,64 @@ class MCPTool(Tool):
48
41
  if not mcp_schema:
49
42
  return {"type": "object", "properties": {}, "required": []}
50
43
 
51
- logger.info(f"Starting schema conversion for {self.name}")
52
- logger.info(f"Initial schema: {json.dumps(mcp_schema, indent=2)}")
44
+ # Create a new schema object to ensure we have all required fields
45
+ strict_schema = {
46
+ "type": "object",
47
+ "properties": {},
48
+ "required": []
49
+ }
53
50
 
54
- # If schema has a $ref, resolve it
55
- if "$ref" in mcp_schema:
56
- ref = mcp_schema["$ref"].split("/")[-1]
57
- mcp_schema = mcp_schema.get("$defs", {}).get(ref, {})
58
- logger.info(f"Resolved $ref to: {json.dumps(mcp_schema, indent=2)}")
51
+ # Add additionalProperties: false for strict tools
52
+ if self.strict:
53
+ strict_schema["additionalProperties"] = False
59
54
 
60
- # If schema has an input wrapper, unwrap it
55
+ # Handle input wrapper
61
56
  if "properties" in mcp_schema and "input" in mcp_schema["properties"]:
62
57
  input_schema = mcp_schema["properties"]["input"]
58
+
59
+ # If input has a $ref, resolve it
63
60
  if "$ref" in input_schema:
64
61
  ref = input_schema["$ref"].split("/")[-1]
65
62
  input_schema = mcp_schema.get("$defs", {}).get(ref, {})
66
- mcp_schema = input_schema
67
- logger.info(f"Unwrapped input schema: {json.dumps(mcp_schema, indent=2)}")
68
-
69
- # Create a new schema object to ensure we have all required fields
70
- strict_schema = {
71
- "type": "object",
72
- "properties": {},
73
- "required": []
74
- }
63
+
64
+ # Create the input property schema
65
+ input_prop_schema = {
66
+ "type": "object",
67
+ "properties": {},
68
+ "required": []
69
+ }
70
+
71
+ # Add additionalProperties: false for input schema
72
+ if self.strict:
73
+ input_prop_schema["additionalProperties"] = False
74
+
75
+ # Copy over input properties
76
+ if "properties" in input_schema:
77
+ for prop_name, prop in input_schema["properties"].items():
78
+ # Create a new property object with required fields
79
+ new_prop = {
80
+ "type": prop.get("type", "string"),
81
+ "description": prop.get("description", f"The {prop_name} parameter")
82
+ }
83
+
84
+ # Copy over any additional fields that might be useful
85
+ for key, value in prop.items():
86
+ if key not in new_prop:
87
+ new_prop[key] = value
88
+
89
+ input_prop_schema["properties"][prop_name] = new_prop
90
+
91
+ # Copy over required fields for input schema
92
+ if "required" in input_schema:
93
+ input_prop_schema["required"] = input_schema["required"]
94
+
95
+ # Add the input property to the main schema
96
+ strict_schema["properties"]["input"] = input_prop_schema
97
+
98
+ # Copy over required fields for main schema
99
+ if "required" in mcp_schema:
100
+ strict_schema["required"] = mcp_schema["required"]
75
101
 
76
- # Copy over properties, ensuring each has type and description
77
- if "properties" in mcp_schema:
78
- for prop_name, prop in mcp_schema["properties"].items():
79
- # Create a new property object with required fields
80
- new_prop = {
81
- "type": prop.get("type", "string"),
82
- "description": prop.get("description", f"The {prop_name} parameter")
83
- }
84
-
85
- # Copy over any additional fields that might be useful
86
- for key, value in prop.items():
87
- if key not in new_prop:
88
- new_prop[key] = value
89
-
90
- strict_schema["properties"][prop_name] = new_prop
91
- logger.info(f"Added property {prop_name}: {json.dumps(new_prop, indent=2)}")
92
-
93
- # Copy over required fields
94
- if "required" in mcp_schema:
95
- strict_schema["required"] = mcp_schema["required"]
96
- logger.info(f"Added required fields: {strict_schema['required']}")
97
-
98
- # Ensure all required fields are actually in properties
99
- strict_schema["required"] = [
100
- req for req in strict_schema["required"]
101
- if req in strict_schema["properties"]
102
- ]
103
- logger.info(f"Final required fields: {strict_schema['required']}")
104
-
105
- logger.info(f"Final strict schema: {json.dumps(strict_schema, indent=2)}")
106
102
  return strict_schema
107
103
 
108
104
 
@@ -175,7 +171,7 @@ class MCPClient(ToolClient):
175
171
 
176
172
  # Register each tool
177
173
  for idx, tool_data in enumerate(tools_data):
178
- logger.info(f"Processing tool {idx}: {json.dumps(tool_data, indent=2)}")
174
+ logger.debug(f"Processing tool {idx}: {json.dumps(tool_data, indent=2)}")
179
175
 
180
176
  # Ensure tool_data is a dictionary
181
177
  if not isinstance(tool_data, dict):
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="0.5.25",
34
+ version="0.6.20",
35
35
  )
36
36
 
37
37
  # Initialize MCP server
mbxai/tools/__init__.py CHANGED
@@ -3,10 +3,11 @@ Tools module for MBX AI.
3
3
  """
4
4
 
5
5
  from .client import ToolClient
6
- from .types import Tool, ToolCall
6
+ from .types import Tool, ToolCall, convert_to_strict_schema
7
7
 
8
8
  __all__ = [
9
9
  "ToolClient",
10
10
  "Tool",
11
11
  "ToolCall",
12
+ "convert_to_strict_schema",
12
13
  ]
mbxai/tools/types.py CHANGED
@@ -4,66 +4,63 @@ Type definitions for the tools package.
4
4
 
5
5
  from typing import Any, Callable
6
6
  from pydantic import BaseModel, Field
7
+ import logging
8
+ import json
7
9
 
8
- class ToolCall(BaseModel):
9
- """A tool call from the model."""
10
- id: str
11
- name: str
12
- arguments: dict[str, Any]
10
+ logger = logging.getLogger(__name__)
13
11
 
14
- class Tool(BaseModel):
15
- """A tool that can be used by the model."""
16
- name: str
17
- description: str
18
- function: Callable[..., Any]
19
- schema: dict[str, Any]
20
-
21
- def to_openai_function(self) -> dict[str, Any]:
22
- """Convert the tool to an OpenAI function definition."""
23
- # Ensure schema is in strict format
24
- strict_schema = self._ensure_strict_schema(self.schema)
12
+ def convert_to_strict_schema(schema: dict[str, Any], strict: bool = True) -> dict[str, Any]:
13
+ """Convert a schema to strict format required by OpenAI.
14
+
15
+ Args:
16
+ schema: The input schema to validate and convert
17
+ strict: Whether to enforce strict validation with additionalProperties: false
25
18
 
26
- return {
27
- "type": "function",
28
- "function": {
29
- "name": self.name,
30
- "description": self.description,
31
- "parameters": strict_schema
32
- }
33
- }
19
+ Returns:
20
+ A schema in strict format
21
+ """
22
+ logger.info(f"Converting schema to strict format. Input schema: {json.dumps(schema, indent=2)}")
23
+ logger.info(f"Strict mode: {strict}")
34
24
 
35
- def _ensure_strict_schema(self, schema: dict[str, Any]) -> dict[str, Any]:
36
- """Ensure the schema is in strict format required by OpenAI.
37
-
38
- Args:
39
- schema: The input schema to validate and convert
40
-
41
- Returns:
42
- A schema in strict format
43
- """
44
- # If schema has a $ref, resolve it
45
- if "$ref" in schema:
46
- ref = schema["$ref"].split("/")[-1]
47
- schema = schema.get("$defs", {}).get(ref, {})
25
+ if not schema:
26
+ return {"type": "object", "properties": {}, "required": []}
48
27
 
49
- # If schema has an input wrapper, unwrap it
50
- if "properties" in schema and "input" in schema["properties"]:
51
- input_schema = schema["properties"]["input"]
52
- if "$ref" in input_schema:
53
- ref = input_schema["$ref"].split("/")[-1]
54
- input_schema = schema.get("$defs", {}).get(ref, {})
55
- schema = input_schema
28
+ # Create a new schema object to ensure we have all required fields
29
+ strict_schema = {
30
+ "type": "object",
31
+ "properties": {},
32
+ "required": []
33
+ }
56
34
 
57
- # Create a new schema object to ensure we have all required fields
58
- strict_schema = {
35
+ # Add additionalProperties: false for strict validation
36
+ if strict:
37
+ strict_schema["additionalProperties"] = False
38
+
39
+ # Handle input wrapper
40
+ if "properties" in schema and "input" in schema["properties"]:
41
+ input_schema = schema["properties"]["input"]
42
+ logger.info(f"Found input wrapper. Input schema: {json.dumps(input_schema, indent=2)}")
43
+
44
+ # If input has a $ref, resolve it
45
+ if "$ref" in input_schema:
46
+ ref = input_schema["$ref"].split("/")[-1]
47
+ input_schema = schema.get("$defs", {}).get(ref, {})
48
+ logger.info(f"Resolved $ref to: {json.dumps(input_schema, indent=2)}")
49
+
50
+ # Create the input property schema
51
+ input_prop_schema = {
59
52
  "type": "object",
60
53
  "properties": {},
61
54
  "required": []
62
55
  }
63
-
64
- # Copy over properties, ensuring each has type and description
65
- if "properties" in schema:
66
- for prop_name, prop in schema["properties"].items():
56
+
57
+ # Add additionalProperties: false for input schema
58
+ if strict:
59
+ input_prop_schema["additionalProperties"] = False
60
+
61
+ # Copy over input properties
62
+ if "properties" in input_schema:
63
+ for prop_name, prop in input_schema["properties"].items():
67
64
  # Create a new property object with required fields
68
65
  new_prop = {
69
66
  "type": prop.get("type", "string"),
@@ -75,21 +72,49 @@ class Tool(BaseModel):
75
72
  if key not in new_prop:
76
73
  new_prop[key] = value
77
74
 
78
- strict_schema["properties"][prop_name] = new_prop
79
-
80
- # Copy over required fields
75
+ input_prop_schema["properties"][prop_name] = new_prop
76
+ logger.info(f"Added property {prop_name}: {json.dumps(new_prop, indent=2)}")
77
+
78
+ # Copy over required fields for input schema
79
+ if "required" in input_schema:
80
+ input_prop_schema["required"] = input_schema["required"]
81
+ logger.info(f"Added required fields for input schema: {input_prop_schema['required']}")
82
+
83
+ # Add the input property to the main schema
84
+ strict_schema["properties"]["input"] = input_prop_schema
85
+
86
+ # Copy over required fields for main schema
81
87
  if "required" in schema:
82
88
  strict_schema["required"] = schema["required"]
89
+ logger.info(f"Added required fields for main schema: {strict_schema['required']}")
90
+
91
+ logger.info(f"Final strict schema: {json.dumps(strict_schema, indent=2)}")
92
+ return strict_schema
83
93
 
84
- # Ensure all required fields are actually in properties
85
- strict_schema["required"] = [
86
- req for req in strict_schema["required"]
87
- if req in strict_schema["properties"]
88
- ]
94
+ class ToolCall(BaseModel):
95
+ """A tool call from the model."""
96
+ id: str
97
+ name: str
98
+ arguments: dict[str, Any]
89
99
 
90
- # Add any additional fields from the original schema
91
- for key, value in schema.items():
92
- if key not in strict_schema:
93
- strict_schema[key] = value
100
+ class Tool(BaseModel):
101
+ """A tool that can be used by the model."""
102
+ name: str
103
+ description: str
104
+ function: Callable[..., Any]
105
+ schema: dict[str, Any]
94
106
 
95
- return strict_schema
107
+ def to_openai_function(self) -> dict[str, Any]:
108
+ """Convert the tool to an OpenAI function definition."""
109
+ # Ensure schema is in strict format
110
+ strict_schema = convert_to_strict_schema(self.schema)
111
+ logger.info(f"Converted schema for {self.name}: {json.dumps(strict_schema, indent=2)}")
112
+
113
+ return {
114
+ "type": "function",
115
+ "function": {
116
+ "name": self.name,
117
+ "description": self.description,
118
+ "parameters": strict_schema
119
+ }
120
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mbxai
3
- Version: 0.6.18
3
+ Version: 0.6.20
4
4
  Summary: MBX AI SDK
5
5
  Project-URL: Homepage, https://www.mibexx.de
6
6
  Project-URL: Documentation, https://www.mibexx.de
@@ -22,6 +22,7 @@ Requires-Dist: pydantic>=2.9.1
22
22
  Requires-Dist: python-multipart>=0.0.20
23
23
  Requires-Dist: sse-starlette>=2.3.4
24
24
  Requires-Dist: starlette>=0.46.2
25
+ Requires-Dist: typing-inspection<=0.4.0
25
26
  Requires-Dist: uvicorn>=0.34.2
26
27
  Provides-Extra: dev
27
28
  Requires-Dist: black>=24.3.0; extra == 'dev'
@@ -1,18 +1,18 @@
1
- mbxai/__init__.py,sha256=TPQPmobkBuhgo14NoMCbpjcR4Lgi3T-hc27EY0cDggk,48
1
+ mbxai/__init__.py,sha256=k5Jl-pIwvz4w-b1_h3wR6y9AzjIl18Pwd10AZN-l-rk,48
2
2
  mbxai/core.py,sha256=WMvmU9TTa7M_m-qWsUew4xH8Ul6xseCZ2iBCXJTW-Bs,196
3
3
  mbxai/mcp/__init__.py,sha256=_ek9iYdYqW5saKetj4qDci11jxesQDiHPJRpHMKkxgU,175
4
- mbxai/mcp/client.py,sha256=_FKq0oayE4eMklHdeMapBzJhzdJ0yAvGYz81-RB5GQY,8408
4
+ mbxai/mcp/client.py,sha256=UhrtsR13px-3MyfHvida5Eh_ou9-ofVexHkhMUEmYH8,7748
5
5
  mbxai/mcp/example.py,sha256=oaol7AvvZnX86JWNz64KvPjab5gg1VjVN3G8eFSzuaE,2350
6
- mbxai/mcp/server.py,sha256=T0-Y7FeHRFqSTp2ERU96fOQlQJKjMFxg8oqC4dzBmBA,3463
6
+ mbxai/mcp/server.py,sha256=65DvS6RyZ59tRThiFo_bCm3C_SbagGtzkTKEAh2JLLM,3463
7
7
  mbxai/openrouter/__init__.py,sha256=Ito9Qp_B6q-RLGAQcYyTJVWwR2YAZvNqE-HIYXxhtD8,298
8
8
  mbxai/openrouter/client.py,sha256=nusxYObyLAMGQd6J9u2uLfkRXyE5kZmdIGdlU-76HPo,13529
9
9
  mbxai/openrouter/config.py,sha256=Ia93s-auim9Sq71eunVDbn9ET5xX2zusXpV4JBdHAzs,3251
10
10
  mbxai/openrouter/models.py,sha256=b3IjjtZAjeGOf2rLsdnCD1HacjTnS8jmv_ZXorc-KJQ,2604
11
- mbxai/tools/__init__.py,sha256=QUFaXhDm-UKcuAtT1rbKzhBkvyRBVokcQIOf9cxIuwc,160
11
+ mbxai/tools/__init__.py,sha256=ogxrHvgJ7OR62Lmd5x9Eh5d2C0jqWyQis7Zy3yKpZ78,218
12
12
  mbxai/tools/client.py,sha256=qOf8MiQ8_flPUNiMioOyjeEaV8rN1EBWb98T8qTC3D4,17582
13
13
  mbxai/tools/example.py,sha256=1HgKK39zzUuwFbnp3f0ThyWVfA_8P28PZcTwaUw5K78,2232
14
- mbxai/tools/types.py,sha256=pFSV59rebWASnIQb27wVg1kFYbCGZmPlOjrLNfNuvrc,3245
15
- mbxai-0.6.18.dist-info/METADATA,sha256=LsCxFm_0dH1JcYPmOMBZZhYXOk0TiO3HPh69RyhjLXk,4108
16
- mbxai-0.6.18.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
17
- mbxai-0.6.18.dist-info/licenses/LICENSE,sha256=hEyhc4FxwYo3NQ40yNgZ7STqwVk-1_XcTXOnAPbGJAw,1069
18
- mbxai-0.6.18.dist-info/RECORD,,
14
+ mbxai/tools/types.py,sha256=lAmy35IX04QV18Am8Ebd-0o6i34QU0vv82FaBYznu_E,4375
15
+ mbxai-0.6.20.dist-info/METADATA,sha256=BQJaUY0ZGe924fILDvEIPefVaPPyu66BFSWZ9Rc-JS8,4148
16
+ mbxai-0.6.20.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
17
+ mbxai-0.6.20.dist-info/licenses/LICENSE,sha256=hEyhc4FxwYo3NQ40yNgZ7STqwVk-1_XcTXOnAPbGJAw,1069
18
+ mbxai-0.6.20.dist-info/RECORD,,
File without changes