mbxai 1.4.0__tar.gz → 1.6.0__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 (34) hide show
  1. {mbxai-1.4.0 → mbxai-1.6.0}/.gitignore +2 -1
  2. {mbxai-1.4.0 → mbxai-1.6.0}/PKG-INFO +1 -1
  3. {mbxai-1.4.0 → mbxai-1.6.0}/pyproject.toml +2 -2
  4. {mbxai-1.4.0 → mbxai-1.6.0}/setup.py +1 -1
  5. {mbxai-1.4.0 → mbxai-1.6.0}/src/mbxai/__init__.py +1 -1
  6. {mbxai-1.4.0 → mbxai-1.6.0}/src/mbxai/mcp/server.py +4 -7
  7. mbxai-1.6.0/src/mbxai/tools/types.py +245 -0
  8. mbxai-1.6.0/tests/test_mcp_tool_registration.py +221 -0
  9. mbxai-1.6.0/tests/test_real_mcp_schema.py +308 -0
  10. mbxai-1.6.0/tests/test_schema_conversion.py +277 -0
  11. mbxai-1.4.0/src/mbxai/tools/types.py +0 -147
  12. {mbxai-1.4.0 → mbxai-1.6.0}/LICENSE +0 -0
  13. {mbxai-1.4.0 → mbxai-1.6.0}/README.md +0 -0
  14. {mbxai-1.4.0 → mbxai-1.6.0}/src/mbxai/core.py +0 -0
  15. {mbxai-1.4.0 → mbxai-1.6.0}/src/mbxai/examples/mcp/mcp_client_example.py +0 -0
  16. {mbxai-1.4.0 → mbxai-1.6.0}/src/mbxai/examples/mcp/mcp_server_example.py +0 -0
  17. {mbxai-1.4.0 → mbxai-1.6.0}/src/mbxai/examples/openrouter_example.py +0 -0
  18. {mbxai-1.4.0 → mbxai-1.6.0}/src/mbxai/examples/parse_example.py +0 -0
  19. {mbxai-1.4.0 → mbxai-1.6.0}/src/mbxai/examples/parse_tool_example.py +0 -0
  20. {mbxai-1.4.0 → mbxai-1.6.0}/src/mbxai/examples/request.json +0 -0
  21. {mbxai-1.4.0 → mbxai-1.6.0}/src/mbxai/examples/response.json +0 -0
  22. {mbxai-1.4.0 → mbxai-1.6.0}/src/mbxai/examples/send_request.py +0 -0
  23. {mbxai-1.4.0 → mbxai-1.6.0}/src/mbxai/examples/tool_client_example.py +0 -0
  24. {mbxai-1.4.0 → mbxai-1.6.0}/src/mbxai/mcp/__init__.py +0 -0
  25. {mbxai-1.4.0 → mbxai-1.6.0}/src/mbxai/mcp/client.py +0 -0
  26. {mbxai-1.4.0 → mbxai-1.6.0}/src/mbxai/mcp/example.py +0 -0
  27. {mbxai-1.4.0 → mbxai-1.6.0}/src/mbxai/openrouter/__init__.py +0 -0
  28. {mbxai-1.4.0 → mbxai-1.6.0}/src/mbxai/openrouter/client.py +0 -0
  29. {mbxai-1.4.0 → mbxai-1.6.0}/src/mbxai/openrouter/config.py +0 -0
  30. {mbxai-1.4.0 → mbxai-1.6.0}/src/mbxai/openrouter/models.py +0 -0
  31. {mbxai-1.4.0 → mbxai-1.6.0}/src/mbxai/openrouter/schema.py +0 -0
  32. {mbxai-1.4.0 → mbxai-1.6.0}/src/mbxai/tools/__init__.py +0 -0
  33. {mbxai-1.4.0 → mbxai-1.6.0}/src/mbxai/tools/client.py +0 -0
  34. {mbxai-1.4.0 → mbxai-1.6.0}/src/mbxai/tools/example.py +0 -0
@@ -92,4 +92,5 @@ wheels/
92
92
  # Project specific
93
93
  *.db
94
94
  *.sqlite3
95
- node_modules/
95
+ node_modules/
96
+ tmp/
@@ -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
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "mbxai"
7
- version = "1.4.0"
7
+ version = "1.6.0"
8
8
  authors = [
9
9
  { name = "MBX AI" }
10
10
  ]
@@ -82,6 +82,6 @@ strict_equality = true
82
82
 
83
83
  [dependency-groups]
84
84
  dev = [
85
- "build>=1.4.0.post1",
85
+ "build>=1.6.0.post1",
86
86
  "twine>=6.1.0",
87
87
  ]
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name="mbxai",
5
- version="1.4.0",
5
+ version="1.6.0",
6
6
  author="MBX AI",
7
7
  description="MBX AI SDK",
8
8
  long_description=open("README.md").read(),
@@ -2,4 +2,4 @@
2
2
  MBX AI package.
3
3
  """
4
4
 
5
- __version__ = "1.4.0"
5
+ __version__ = "1.6.0"
@@ -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,
@@ -0,0 +1,245 @@
1
+ """
2
+ Type definitions for the tools package.
3
+ """
4
+
5
+ from typing import Any, Callable
6
+ from pydantic import BaseModel, Field
7
+ import logging
8
+ import json
9
+
10
+ logger = logging.getLogger(__name__)
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
+
132
+ def convert_to_strict_schema(schema: dict[str, Any], strict: bool = True, keep_input_wrapper: bool = False) -> dict[str, Any]:
133
+ """Convert a schema to strict format required by OpenAI.
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
+
141
+ Args:
142
+ schema: The input schema to validate and convert
143
+ strict: Whether to enforce strict validation with additionalProperties: false
144
+ keep_input_wrapper: Whether to keep the input wrapper (for MCP tools)
145
+
146
+ Returns:
147
+ A schema in strict format with all references resolved
148
+ """
149
+ if not schema:
150
+ return {"type": "object", "properties": {}, "required": [], "additionalProperties": False}
151
+
152
+ # Create a new schema object to ensure we have all required fields
153
+ strict_schema = {
154
+ "type": "object",
155
+ "properties": {},
156
+ "required": [],
157
+ "additionalProperties": False # Always enforce additionalProperties: false for OpenRouter
158
+ }
159
+
160
+ # Store the root schema for $ref resolution
161
+ root_schema = schema
162
+
163
+ # Handle input wrapper
164
+ if "properties" in schema and "input" in schema["properties"]:
165
+ inputSchema = schema["properties"]["input"]
166
+
167
+ # If input has a $ref, resolve it
168
+ if "$ref" in inputSchema:
169
+ inputSchema = _resolve_ref(inputSchema, inputSchema["$ref"], root_schema)
170
+
171
+ if keep_input_wrapper:
172
+ # Create the input property schema
173
+ input_prop_schema = {
174
+ "type": "object",
175
+ "properties": {},
176
+ "required": [],
177
+ "additionalProperties": False # Always enforce additionalProperties: false for OpenRouter
178
+ }
179
+
180
+ # Process input properties
181
+ if "properties" in inputSchema:
182
+ for prop_name, prop in inputSchema["properties"].items():
183
+ input_prop_schema["properties"][prop_name] = _convert_property_to_strict(prop, root_schema)
184
+
185
+ # Copy over required fields for input schema
186
+ if "required" in inputSchema:
187
+ input_prop_schema["required"] = inputSchema["required"]
188
+
189
+ # Add the input property to the main schema
190
+ strict_schema["properties"]["input"] = input_prop_schema
191
+
192
+ # Copy over required fields for main schema
193
+ if "required" in schema:
194
+ strict_schema["required"] = schema["required"]
195
+ else:
196
+ # If not keeping input wrapper, use input schema directly
197
+ if "properties" in inputSchema:
198
+ for prop_name, prop in inputSchema["properties"].items():
199
+ strict_schema["properties"][prop_name] = _convert_property_to_strict(prop, root_schema)
200
+
201
+ # Copy over required fields
202
+ if "required" in inputSchema:
203
+ strict_schema["required"] = inputSchema["required"]
204
+ else:
205
+ # If no input wrapper, use the schema as is
206
+ if "properties" in schema:
207
+ for prop_name, prop in schema["properties"].items():
208
+ strict_schema["properties"][prop_name] = _convert_property_to_strict(prop, root_schema)
209
+
210
+ # Copy over required fields
211
+ if "required" in schema:
212
+ strict_schema["required"] = schema["required"]
213
+
214
+ return strict_schema
215
+
216
+ class ToolCall(BaseModel):
217
+ """A tool call from the model."""
218
+ id: str
219
+ name: str
220
+ arguments: dict[str, Any]
221
+
222
+ class Tool(BaseModel):
223
+ """A tool that can be used by the model."""
224
+ name: str
225
+ description: str
226
+ function: Callable[..., Any] | None = None # Make function optional
227
+ schema: dict[str, Any]
228
+
229
+ def to_openai_function(self) -> dict[str, Any]:
230
+ """Convert the tool to an OpenAI function definition."""
231
+ # Ensure schema is in strict format
232
+ strict_schema = convert_to_strict_schema(self.schema)
233
+
234
+ function_def = {
235
+ "type": "function",
236
+ "function": {
237
+ "name": self.name,
238
+ "description": self.description,
239
+ "parameters": strict_schema,
240
+ "strict": True
241
+ }
242
+ }
243
+
244
+ logger.debug(f"(types) Created function definition for {self.name}: {json.dumps(function_def, indent=2)}")
245
+ return function_def
@@ -0,0 +1,221 @@
1
+ """
2
+ Test MCP tool registration flow to identify where $ref schemas are still used.
3
+
4
+ This test simulates the server-client registration process to find the bug.
5
+ """
6
+
7
+ import json
8
+ import os
9
+ from typing import Any
10
+ from pydantic import BaseModel, Field
11
+
12
+ # Add the src directory to the path so we can import from mbxai
13
+ import sys
14
+ from pathlib import Path
15
+ sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
16
+
17
+ from mbxai.mcp.client import MCPTool
18
+ from mbxai.tools.types import convert_to_strict_schema
19
+
20
+
21
+ class MetadataFilter(BaseModel):
22
+ """Model for a single metadata filter key-value pair."""
23
+ key: str = Field(description="The metadata field name to filter by")
24
+ value: Any = Field(description="The value to filter for")
25
+
26
+
27
+ class ShopwareKnowledgeSearchInput(BaseModel):
28
+ """Input model for Shopware knowledge search."""
29
+
30
+ query: str = Field(description="The search query")
31
+ max_results: int = Field(description="Maximum results (1-20)", ge=1, le=20)
32
+ include_metadata: bool = Field(description="Whether to include metadata")
33
+ metadata_filter: list[MetadataFilter] = Field(description="List of metadata filters")
34
+
35
+
36
+ def test_mcp_tool_registration_flow():
37
+ """Test the exact MCP tool registration flow to identify $ref issues."""
38
+
39
+ print("🔍 Testing MCP Tool Registration Flow")
40
+ print("=" * 50)
41
+
42
+ # Step 1: Simulate what FastMCP generates (like what MCP server gets)
43
+ print("\n1. Simulating FastMCP schema generation...")
44
+ fastmcp_schema = ShopwareKnowledgeSearchInput.model_json_schema()
45
+
46
+ print("FastMCP Raw Schema:")
47
+ print(json.dumps(fastmcp_schema, indent=2))
48
+
49
+ # Create output directory
50
+ tmp_dir = Path(__file__).parent.parent / "tmp"
51
+ tmp_dir.mkdir(exist_ok=True)
52
+
53
+ # Save FastMCP schema
54
+ with open(tmp_dir / "fastmcp_raw_schema.json", 'w') as f:
55
+ json.dump(fastmcp_schema, f, indent=2)
56
+
57
+ # Step 2: Simulate what MCP server stores (what goes in Tool.inputSchema)
58
+ print("\n2. What MCP server stores in Tool.inputSchema...")
59
+ mcp_server_stored_schema = fastmcp_schema # Server just stores the raw schema
60
+
61
+ with open(tmp_dir / "mcp_server_stored_schema.json", 'w') as f:
62
+ json.dump(mcp_server_stored_schema, f, indent=2)
63
+
64
+ # Step 3: Simulate what /tools endpoint returns
65
+ print("\n3. What /tools endpoint returns...")
66
+ tools_endpoint_response = {
67
+ "name": "search_shopware_knowledge",
68
+ "description": "Search Shopware knowledge base",
69
+ "inputSchema": mcp_server_stored_schema, # This has $ref!
70
+ "internal_url": "http://localhost:8000/tools/search_shopware_knowledge/invoke",
71
+ "service": "shopware-search",
72
+ "strict": True
73
+ }
74
+
75
+ print("Tools endpoint response:")
76
+ print(json.dumps(tools_endpoint_response, indent=2))
77
+
78
+ with open(tmp_dir / "tools_endpoint_response.json", 'w') as f:
79
+ json.dump(tools_endpoint_response, f, indent=2)
80
+
81
+ # Step 4: Simulate MCPClient creating MCPTool
82
+ print("\n4. MCPClient creating MCPTool...")
83
+ try:
84
+ # This is what MCPClient.register_mcp_server() does
85
+ mcp_tool = MCPTool(**tools_endpoint_response)
86
+
87
+ print("✅ MCPTool created successfully")
88
+ print(f"MCPTool.inputSchema: {type(mcp_tool.inputSchema)}")
89
+
90
+ # Check if inputSchema still has $ref
91
+ schema_str = json.dumps(mcp_tool.inputSchema)
92
+ has_ref = "$ref" in schema_str or "$defs" in schema_str
93
+ print(f"❌ MCPTool.inputSchema still has $ref/$defs: {has_ref}")
94
+
95
+ if has_ref:
96
+ print(" 🚨 PROBLEM: Raw schema with $ref is stored in MCPTool!")
97
+
98
+ with open(tmp_dir / "mcp_tool_input_schema.json", 'w') as f:
99
+ json.dump(mcp_tool.inputSchema, f, indent=2)
100
+
101
+ except Exception as e:
102
+ print(f"❌ Failed to create MCPTool: {e}")
103
+ return
104
+
105
+ # Step 5: Test MCPTool.to_openai_function()
106
+ print("\n5. Testing MCPTool.to_openai_function()...")
107
+ try:
108
+ openai_function = mcp_tool.to_openai_function()
109
+
110
+ print("✅ OpenAI function created")
111
+
112
+ # Check if the converted schema still has $ref
113
+ function_schema_str = json.dumps(openai_function["function"]["parameters"])
114
+ has_ref_after = "$ref" in function_schema_str or "$defs" in function_schema_str
115
+
116
+ print(f"❌ Final OpenAI schema still has $ref/$defs: {has_ref_after}")
117
+
118
+ if has_ref_after:
119
+ print(" 🚨 CRITICAL PROBLEM: convert_to_strict_schema didn't resolve $ref!")
120
+ else:
121
+ print(" ✅ convert_to_strict_schema successfully resolved $ref")
122
+
123
+ # Check if arrays have items
124
+ def check_arrays_in_schema(schema, path=""):
125
+ issues = []
126
+ if isinstance(schema, dict):
127
+ if schema.get("type") == "array":
128
+ if "items" not in schema:
129
+ issues.append(f"Array at {path} missing items")
130
+ else:
131
+ print(f" ✅ Array at {path} has items")
132
+
133
+ for key, value in schema.items():
134
+ if key == "properties" and isinstance(value, dict):
135
+ for prop_name, prop_schema in value.items():
136
+ issues.extend(check_arrays_in_schema(prop_schema, f"{path}.{prop_name}" if path else prop_name))
137
+ elif key == "items" and isinstance(value, dict):
138
+ issues.extend(check_arrays_in_schema(value, f"{path}.items"))
139
+ return issues
140
+
141
+ array_issues = check_arrays_in_schema(openai_function["function"]["parameters"])
142
+ if array_issues:
143
+ for issue in array_issues:
144
+ print(f" ❌ {issue}")
145
+
146
+ with open(tmp_dir / "final_openai_function.json", 'w') as f:
147
+ json.dump(openai_function, f, indent=2)
148
+
149
+ print(f"\n📁 Files created in {tmp_dir}:")
150
+ print(" - fastmcp_raw_schema.json")
151
+ print(" - mcp_server_stored_schema.json")
152
+ print(" - tools_endpoint_response.json")
153
+ print(" - mcp_tool_input_schema.json")
154
+ print(" - final_openai_function.json")
155
+
156
+ return openai_function
157
+
158
+ except Exception as e:
159
+ print(f"❌ Failed to create OpenAI function: {e}")
160
+ import traceback
161
+ traceback.print_exc()
162
+ return None
163
+
164
+ def test_direct_schema_conversion():
165
+ """Test direct schema conversion to compare with MCP flow."""
166
+
167
+ print("\n\n🔧 Testing Direct Schema Conversion (for comparison)")
168
+ print("=" * 55)
169
+
170
+ # Generate the schema
171
+ schema = ShopwareKnowledgeSearchInput.model_json_schema()
172
+
173
+ print("\n1. Direct conversion without input wrapper...")
174
+ strict_schema = convert_to_strict_schema(schema, strict=True, keep_input_wrapper=False)
175
+
176
+ schema_str = json.dumps(strict_schema)
177
+ has_ref = "$ref" in schema_str or "$defs" in schema_str
178
+ print(f" Direct conversion has $ref/$defs: {has_ref}")
179
+
180
+ print("\n2. Direct conversion with input wrapper...")
181
+ # Simulate MCP-style wrapper
182
+ wrapped_schema = {
183
+ "type": "object",
184
+ "properties": {
185
+ "input": schema
186
+ },
187
+ "required": ["input"]
188
+ }
189
+
190
+ strict_wrapped = convert_to_strict_schema(wrapped_schema, strict=True, keep_input_wrapper=True)
191
+
192
+ wrapped_str = json.dumps(strict_wrapped)
193
+ has_ref_wrapped = "$ref" in wrapped_str or "$defs" in wrapped_str
194
+ print(f" Wrapped conversion has $ref/$defs: {has_ref_wrapped}")
195
+
196
+ # Save for comparison
197
+ tmp_dir = Path(__file__).parent.parent / "tmp"
198
+ with open(tmp_dir / "direct_conversion_no_wrapper.json", 'w') as f:
199
+ json.dump(strict_schema, f, indent=2)
200
+
201
+ with open(tmp_dir / "direct_conversion_with_wrapper.json", 'w') as f:
202
+ json.dump(strict_wrapped, f, indent=2)
203
+
204
+
205
+ if __name__ == "__main__":
206
+ try:
207
+ # Test the MCP registration flow
208
+ result = test_mcp_tool_registration_flow()
209
+
210
+ # Test direct conversion for comparison
211
+ test_direct_schema_conversion()
212
+
213
+ if result:
214
+ print("\n✅ MCP flow test completed - check the JSON files for details")
215
+ else:
216
+ print("\n❌ MCP flow test failed")
217
+
218
+ except Exception as e:
219
+ print(f"\n💥 Test failed with error: {e}")
220
+ import traceback
221
+ traceback.print_exc()