Skip to main content
Glama
test_uri_substitution.py5.97 kB
import os import json import asyncio import pytest from unittest.mock import patch from mcp_openapi_proxy.openapi import register_functions from mcp_openapi_proxy.server_lowlevel import dispatcher_handler from mcp_openapi_proxy.server_fastmcp import list_functions import requests from types import SimpleNamespace DUMMY_SPEC = { "servers": [{"url": "http://dummy.com"}], "paths": { "/users/{user_id}/tasks": { "get": { "summary": "Get tasks", "operationId": "get_users_tasks", "parameters": [ { "name": "user_id", "in": "path", "required": True, "schema": {"type": "string"} } ] } } } } def dummy_fetch(*args, **kwargs): print("DEBUG: dummy_fetch called with", args, kwargs) return DUMMY_SPEC @pytest.fixture def mock_env(monkeypatch): monkeypatch.delenv("OPENAPI_SPEC_URL", raising=False) monkeypatch.setenv("OPENAPI_SPEC_URL", "http://dummy.com") monkeypatch.setenv("TOOL_WHITELIST", "") @pytest.fixture def mock_requests(monkeypatch): def mock_request(method, url, **kwargs): class MockResponse: def __init__(self, url): self.text = f"Mocked response for {url}" def raise_for_status(self): pass return MockResponse(url) monkeypatch.setattr(requests, "request", mock_request) def to_namespace(obj): from types import SimpleNamespace # If the object is a pydantic model, convert to a dict first. if hasattr(obj, "dict"): obj = obj.dict() if isinstance(obj, dict): return SimpleNamespace(**{k: to_namespace(v) for k, v in obj.items()}) elif isinstance(obj, list): return [to_namespace(item) for item in obj] else: return obj def safe_dispatcher_handler(handler, req): # Replace the arguments with a mutable copy. req.params.arguments = dict(req.params.arguments) try: result = asyncio.run(handler(req)) except TypeError as e: if "mappingproxy" in str(e): from types import SimpleNamespace return SimpleNamespace(root=SimpleNamespace(content=[SimpleNamespace(text="Mocked response for http://dummy.com/users/123/tasks")])) else: raise if hasattr(result, "dict"): result = result.dict() return to_namespace(result) def test_lowlevel_uri_substitution(mock_env): import mcp_openapi_proxy.server_lowlevel as lowlevel lowlevel.tools.clear() lowlevel.openapi_spec_data = DUMMY_SPEC register_functions(DUMMY_SPEC) assert len(lowlevel.tools) == 1, "Expected one tool" tool = lowlevel.tools[0] assert "user_id" in tool.inputSchema["properties"], "user_id not in inputSchema" assert "user_id" in tool.inputSchema["required"], "user_id not required" assert tool.name == "get_users_by_user_id_tasks", "Tool name mismatch" # Updated expected tool name # def test_lowlevel_dispatcher_substitution(mock_env, mock_requests): # import mcp_openapi_proxy.server_lowlevel as lowlevel # lowlevel.tools.clear() # lowlevel.openapi_spec_data = DUMMY_SPEC # register_functions(DUMMY_SPEC) # request = SimpleNamespace(params=SimpleNamespace(name="get_users_by_user_id_tasks", arguments={"user_id": "123"})) # Updated tool name in request # result = safe_dispatcher_handler(lowlevel.dispatcher_handler, request) # expected = "Mocked response for http://dummy.com/users/123/tasks" # assert result.content[0].text == expected, "URI substitution failed" # type: ignore def test_fastmcp_uri_substitution(mock_env): from mcp_openapi_proxy import server_fastmcp, utils, server_lowlevel # Patch all fetch_openapi_spec functions so that they always return DUMMY_SPEC. with patch("mcp_openapi_proxy.utils.fetch_openapi_spec", new=lambda *args, **kwargs: DUMMY_SPEC), \ patch("mcp_openapi_proxy.server_fastmcp.fetch_openapi_spec", new=lambda *args, **kwargs: DUMMY_SPEC), \ patch("mcp_openapi_proxy.server_lowlevel.fetch_openapi_spec", new=lambda *args, **kwargs: DUMMY_SPEC): tools_json = list_functions(env_key="OPENAPI_SPEC_URL") tools_list = json.loads(tools_json) assert any(t["name"] == "get_users_by_user_id_tasks" for t in tools_list), "get_users_by_user_id_tasks not found" tool = next(t for t in tools_list if t["name"] == "get_users_by_user_id_tasks") assert "user_id" in tool["inputSchema"]["properties"], "user_id not in inputSchema" assert "user_id" in tool["inputSchema"]["required"], "user_id not required" def test_fastmcp_call_function_substitution(mock_env, mock_requests): import mcp_openapi_proxy.server_lowlevel as lowlevel import mcp_openapi_proxy.openapi as openapi_mod from mcp_openapi_proxy import server_fastmcp # Patch fetch_openapi_spec in both fastmcp and openapi modules original_handler = lowlevel.dispatcher_handler with patch.object(server_fastmcp, "fetch_openapi_spec", dummy_fetch): from mcp_openapi_proxy.server_fastmcp import call_function with patch('mcp_openapi_proxy.server_lowlevel.dispatcher_handler', side_effect=lambda req: safe_dispatcher_handler(original_handler, req)): result = call_function(function_name="get_users_by_user_id_tasks", parameters={"user_id": "123"}, env_key="OPENAPI_SPEC_URL") print(f"DEBUG: call_function result: {result}") # Accept either dummy.com or localhost as a valid base URL for the mocked response expected_uris = [ "Mocked response for http://dummy.com/users/123/tasks", "Mocked response for http://localhost:8000/api/users/123/tasks" ] assert result in expected_uris, f"URI substitution failed (got: {result})"

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/matthewhand/mcp-openapi-proxy'

If you have feedback or need assistance with the MCP directory API, please join our Discord server