Juergen Eger commited on
Commit
11780c0
·
1 Parent(s): 7657683

#267 Fixed openapi template resource only supporting single param

Browse files
src/fastmcp/server/openapi.py CHANGED
@@ -278,21 +278,22 @@ class OpenAPIResource(Resource):
278
  if "{" in path and "}" in path:
279
  # Extract the resource ID from the URI (the last part after the last slash)
280
  parts = resource_uri.split("/")
 
281
  if len(parts) > 1:
282
  # Find all path parameters in the route path
283
  path_params = {}
284
 
285
- # Extract parameters from the URI
286
- param_value = parts[
287
- -1
288
- ] # The last part contains the parameter value
289
-
290
- # Find the path parameter name from the route path
291
  param_matches = re.findall(r"\{([^}]+)\}", path)
292
  if param_matches:
293
- # Assume the last parameter in the URI is for the first path parameter in the route
294
- path_param_name = param_matches[0]
295
- path_params[path_param_name] = param_value
 
 
 
 
 
296
 
297
  # Replace path parameters with their values
298
  for param_name, param_value in path_params.items():
 
278
  if "{" in path and "}" in path:
279
  # Extract the resource ID from the URI (the last part after the last slash)
280
  parts = resource_uri.split("/")
281
+
282
  if len(parts) > 1:
283
  # Find all path parameters in the route path
284
  path_params = {}
285
 
286
+ # Find the path parameter names from the route path
 
 
 
 
 
287
  param_matches = re.findall(r"\{([^}]+)\}", path)
288
  if param_matches:
289
+ # Number of sent parameters is number of parts -1 (assuming first part is resource identifier)
290
+ expected_param_count = len(parts) -1
291
+ # Map parameters from the end of the URI to the parameters in the path
292
+ # Last parameter in URI (parts[-1]) maps to last parameter in path, and so on
293
+ for i, param_name in enumerate(param_matches):
294
+ if i < len(expected_param_count): # Ensure we don't use resource identifier as parameter
295
+ param_value = parts[-1-i] # Get values from the end of parts
296
+ path_params[param_name] = param_value
297
 
298
  # Replace path parameters with their values
299
  for param_name, param_value in path_params.items():
tests/server/test_openapi.py CHANGED
@@ -50,6 +50,14 @@ def fastapi_app(users_db: dict[int, User]) -> FastAPI:
50
  async def get_user(user_id: int) -> User | None:
51
  """Get a user by ID."""
52
  return users_db.get(user_id)
 
 
 
 
 
 
 
 
53
 
54
  @app.post("/users", tags=["users", "create"])
55
  async def create_user(user: UserCreate) -> User:
@@ -303,12 +311,17 @@ class TestResourceTemplates:
303
  """
304
  async with Client(fastmcp_openapi_server) as client:
305
  resource_templates = await client.list_resource_templates()
306
- assert len(resource_templates) == 1
307
  assert resource_templates[0].name == "get_user_users__user_id__get"
308
  assert (
309
  resource_templates[0].uriTemplate
310
  == r"resource://openapi/get_user_users__user_id__get/{user_id}"
311
  )
 
 
 
 
 
312
 
313
  async def test_get_resource_template(
314
  self,
@@ -333,6 +346,30 @@ class TestResourceTemplates:
333
  assert resource == response.json()
334
 
335
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
336
  class TestPrompts:
337
  async def test_list_prompts(self, fastmcp_openapi_server: FastMCPOpenAPI):
338
  """
@@ -818,7 +855,7 @@ class TestMountFastMCP:
818
  # Check that templates are available with prefixed URIs
819
  async with Client(mcp) as client:
820
  templates = await client.list_resource_templates()
821
- assert len(templates) == 1
822
  assert templates[0].name == "get_user_users__user_id__get"
823
  prefixed_template_uri = (
824
  r"fastapi+resource://openapi/get_user_users__user_id__get/{user_id}"
 
50
  async def get_user(user_id: int) -> User | None:
51
  """Get a user by ID."""
52
  return users_db.get(user_id)
53
+
54
+ @app.get("/users/{user_id}/{is_active}", tags=["users", "detail"])
55
+ async def get_user_active_state(user_id: int, is_active: bool) -> User | None:
56
+ """Get a user by ID."""
57
+ user = users_db.get(user_id)
58
+ if user is not None and user.active == is_active:
59
+ return user
60
+ return None
61
 
62
  @app.post("/users", tags=["users", "create"])
63
  async def create_user(user: UserCreate) -> User:
 
311
  """
312
  async with Client(fastmcp_openapi_server) as client:
313
  resource_templates = await client.list_resource_templates()
314
+ assert len(resource_templates) == 2
315
  assert resource_templates[0].name == "get_user_users__user_id__get"
316
  assert (
317
  resource_templates[0].uriTemplate
318
  == r"resource://openapi/get_user_users__user_id__get/{user_id}"
319
  )
320
+ assert resource_templates[1].name == "get_user_active_state_users__user_id___is_active__get"
321
+ assert (
322
+ resource_templates[1].uriTemplate
323
+ == r"resource://openapi/get_user_active_state_users__user_id___is_active__get/{is_active}/{user_id}"
324
+ )
325
 
326
  async def test_get_resource_template(
327
  self,
 
346
  assert resource == response.json()
347
 
348
 
349
+ async def test_get_resource_template_multi_param(
350
+ self,
351
+ fastmcp_openapi_server: FastMCPOpenAPI,
352
+ api_client,
353
+ users_db: dict[int, User],
354
+ ):
355
+ """
356
+ The resource template created by the OpenAPI server should be the same as the original
357
+ """
358
+ user_id = 2
359
+ is_active = True
360
+ async with Client(fastmcp_openapi_server) as client:
361
+ resource_response = await client.read_resource(
362
+ f"resource://openapi/get_user_users__user_type__user_id__get/{user_id}/{is_active}"
363
+ )
364
+ assert isinstance(resource_response[0], TextResourceContents)
365
+ response_text = resource_response[0].text
366
+ resource = json.loads(response_text)
367
+
368
+ assert resource == users_db[user_id].model_dump()
369
+ response = await api_client.get(f"/users/{user_id}/{is_active}")
370
+ assert resource == response.json()
371
+
372
+
373
  class TestPrompts:
374
  async def test_list_prompts(self, fastmcp_openapi_server: FastMCPOpenAPI):
375
  """
 
855
  # Check that templates are available with prefixed URIs
856
  async with Client(mcp) as client:
857
  templates = await client.list_resource_templates()
858
+ assert len(templates) == 2
859
  assert templates[0].name == "get_user_users__user_id__get"
860
  prefixed_template_uri = (
861
  r"fastapi+resource://openapi/get_user_users__user_id__get/{user_id}"