File size: 15,743 Bytes
8c5bdf2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
"""Tests for deepObject style parameter handling in openapi_new."""

import httpx
import pytest

from fastmcp.client import Client
from fastmcp.experimental.server.openapi import FastMCPOpenAPI


class TestDeepObjectStyle:
    """Test deepObject style parameter handling in openapi_new."""

    @pytest.fixture
    def deepobject_spec(self):
        """OpenAPI spec with deepObject style parameters."""
        return {
            "openapi": "3.0.0",
            "info": {"title": "DeepObject Test API", "version": "1.0.0"},
            "servers": [{"url": "https://api.example.com"}],
            "paths": {
                "/surveys": {
                    "get": {
                        "operationId": "get_surveys",
                        "summary": "Get surveys with deepObject filtering",
                        "parameters": [
                            {
                                "name": "target",
                                "in": "query",
                                "required": False,
                                "style": "deepObject",
                                "explode": True,
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "id": {
                                            "type": "string",
                                            "description": "Target ID",
                                        },
                                        "type": {
                                            "type": "string",
                                            "enum": ["location", "organisation"],
                                            "description": "Target type",
                                        },
                                    },
                                    "required": ["type", "id"],
                                },
                                "description": "Target object for filtering",
                            },
                            {
                                "name": "filters",
                                "in": "query",
                                "required": False,
                                "style": "deepObject",
                                "explode": True,
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "status": {"type": "string"},
                                        "category": {"type": "string"},
                                        "priority": {"type": "integer"},
                                    },
                                },
                                "description": "Additional filters",
                            },
                            {
                                "name": "compact",
                                "in": "query",
                                "required": False,
                                "style": "deepObject",
                                "explode": False,
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "format": {"type": "string"},
                                        "level": {"type": "integer"},
                                    },
                                },
                                "description": "Compact format options (explode=false)",
                            },
                        ],
                        "responses": {
                            "200": {
                                "description": "Survey list",
                                "content": {
                                    "application/json": {
                                        "schema": {
                                            "type": "object",
                                            "properties": {
                                                "surveys": {
                                                    "type": "array",
                                                    "items": {"type": "object"},
                                                },
                                                "total": {"type": "integer"},
                                            },
                                        }
                                    }
                                },
                            }
                        },
                    }
                },
                "/users/{id}/preferences": {
                    "patch": {
                        "operationId": "update_preferences",
                        "summary": "Update user preferences with deepObject in body",
                        "parameters": [
                            {
                                "name": "id",
                                "in": "path",
                                "required": True,
                                "schema": {"type": "integer"},
                            }
                        ],
                        "requestBody": {
                            "required": True,
                            "content": {
                                "application/json": {
                                    "schema": {
                                        "type": "object",
                                        "properties": {
                                            "preferences": {
                                                "type": "object",
                                                "properties": {
                                                    "theme": {"type": "string"},
                                                    "notifications": {
                                                        "type": "object",
                                                        "properties": {
                                                            "email": {
                                                                "type": "boolean"
                                                            },
                                                            "push": {"type": "boolean"},
                                                            "frequency": {
                                                                "type": "string"
                                                            },
                                                        },
                                                    },
                                                    "privacy": {
                                                        "type": "object",
                                                        "properties": {
                                                            "profile_visible": {
                                                                "type": "boolean"
                                                            },
                                                            "analytics": {
                                                                "type": "boolean"
                                                            },
                                                        },
                                                    },
                                                },
                                                "description": "Nested preference object",
                                            }
                                        },
                                        "required": ["preferences"],
                                    }
                                }
                            },
                        },
                        "responses": {
                            "200": {
                                "description": "Preferences updated",
                                "content": {
                                    "application/json": {
                                        "schema": {
                                            "type": "object",
                                            "properties": {
                                                "success": {"type": "boolean"}
                                            },
                                        }
                                    }
                                },
                            }
                        },
                    }
                },
            },
        }

    @pytest.mark.asyncio
    async def test_deepobject_style_parsing_from_spec(self, deepobject_spec):
        """Test that deepObject style parameters are correctly parsed from OpenAPI spec."""
        async with httpx.AsyncClient(base_url="https://api.example.com") as client:
            server = FastMCPOpenAPI(
                openapi_spec=deepobject_spec,
                client=client,
                name="DeepObject Test Server",
            )

            async with Client(server) as mcp_client:
                tools = await mcp_client.list_tools()

                # Find the surveys tool
                surveys_tool = next(
                    tool for tool in tools if tool.name == "get_surveys"
                )
                assert surveys_tool is not None

                # Check that deepObject parameters are included in schema
                params = surveys_tool.inputSchema
                properties = params["properties"]

                # Should have the deepObject parameters
                assert "target" in properties
                assert "filters" in properties
                assert "compact" in properties

                # Check that target parameter is present
                # (Exact schema structure may vary based on implementation)
                target_param = properties["target"]
                # Should have some structure, exact format may vary
                assert target_param is not None

    @pytest.mark.asyncio
    async def test_deepobject_explode_true_handling(self, deepobject_spec):
        """Test deepObject with explode=true parameter handling."""
        async with httpx.AsyncClient(base_url="https://api.example.com") as client:
            server = FastMCPOpenAPI(
                openapi_spec=deepobject_spec,
                client=client,
                name="DeepObject Test Server",
            )

            async with Client(server) as mcp_client:
                tools = await mcp_client.list_tools()
                surveys_tool = next(
                    tool for tool in tools if tool.name == "get_surveys"
                )

                # Check that explode=true parameters are properly structured
                params = surveys_tool.inputSchema
                properties = params["properties"]

                # Target parameter with explode=true should allow individual property access
                target_properties = properties["target"]["properties"]
                assert "id" in target_properties
                assert "type" in target_properties
                assert target_properties["type"]["enum"] == ["location", "organisation"]

    @pytest.mark.asyncio
    async def test_deepobject_explode_false_handling(self, deepobject_spec):
        """Test deepObject with explode=false parameter handling."""
        async with httpx.AsyncClient(base_url="https://api.example.com") as client:
            server = FastMCPOpenAPI(
                openapi_spec=deepobject_spec,
                client=client,
                name="DeepObject Test Server",
            )

            async with Client(server) as mcp_client:
                tools = await mcp_client.list_tools()
                surveys_tool = next(
                    tool for tool in tools if tool.name == "get_surveys"
                )

                # Check that explode=false parameters are handled
                params = surveys_tool.inputSchema
                properties = params["properties"]

                # Compact parameter with explode=false should still be present and valid
                assert "compact" in properties
                compact_param = properties["compact"]
                # Check that it's a valid parameter (exact structure may vary)
                assert compact_param is not None
                # If it has a type, it should be object
                if "type" in compact_param:
                    assert compact_param["type"] == "object"

    @pytest.mark.asyncio
    async def test_nested_object_structure_in_request_body(self, deepobject_spec):
        """Test nested object structures in request body are preserved."""
        async with httpx.AsyncClient(base_url="https://api.example.com") as client:
            server = FastMCPOpenAPI(
                openapi_spec=deepobject_spec,
                client=client,
                name="DeepObject Test Server",
            )

            async with Client(server) as mcp_client:
                tools = await mcp_client.list_tools()

                # Find the preferences tool
                prefs_tool = next(
                    tool for tool in tools if tool.name == "update_preferences"
                )
                assert prefs_tool is not None

                # Check that nested object structure is preserved
                params = prefs_tool.inputSchema
                properties = params["properties"]

                # Should have path parameter
                assert "id" in properties

                # Should have preferences object
                assert "preferences" in properties
                prefs_param = properties["preferences"]
                assert prefs_param["type"] == "object"

                # Check nested structure
                prefs_props = prefs_param["properties"]
                assert "theme" in prefs_props
                assert "notifications" in prefs_props
                assert "privacy" in prefs_props

                # Check deeply nested objects
                notifications = prefs_props["notifications"]
                assert notifications["type"] == "object"
                notif_props = notifications["properties"]
                assert "email" in notif_props
                assert "push" in notif_props
                assert "frequency" in notif_props

    @pytest.mark.asyncio
    async def test_deepobject_tool_functionality(self, deepobject_spec):
        """Test that tools with deepObject parameters maintain basic functionality."""
        async with httpx.AsyncClient(base_url="https://api.example.com") as client:
            server = FastMCPOpenAPI(
                openapi_spec=deepobject_spec,
                client=client,
                name="DeepObject Test Server",
            )

            async with Client(server) as mcp_client:
                tools = await mcp_client.list_tools()

                # Should successfully create tools with deepObject parameters
                assert len(tools) == 2

                tool_names = {tool.name for tool in tools}
                assert "get_surveys" in tool_names
                assert "update_preferences" in tool_names

                # All tools should have valid schemas
                for tool in tools:
                    assert tool.inputSchema is not None
                    assert tool.inputSchema["type"] == "object"
                    assert "properties" in tool.inputSchema

                    # Should have some properties
                    assert len(tool.inputSchema["properties"]) > 0