feat: create tools using MCP

This commit is contained in:
Abhishek Kumar 2026-05-31 16:50:44 +05:30
parent 5c29b6ed94
commit fcb7004c7a
17 changed files with 1989 additions and 572 deletions

View file

@ -16,10 +16,20 @@ Test coverage:
from __future__ import annotations
from unittest.mock import AsyncMock, MagicMock
import pytest
from fastapi import HTTPException
from pydantic import ValidationError
from api.routes.tool import CreateToolRequest, McpToolDefinition, UpdateToolRequest
from api.routes.tool import (
CreateToolRequest,
McpToolConfig,
McpToolDefinition,
UpdateToolRequest,
_populate_discovered_tools,
refresh_mcp_tools,
)
from api.services.workflow.tools.mcp_tool import (
validate_mcp_definition,
)
@ -279,10 +289,6 @@ async def test_post_tool_mcp_invalid_url_returns_422(test_client_factory, db_ses
# ── Task 6: discovered_tools field and _populate_discovered_tools helper ──────
from unittest.mock import AsyncMock, MagicMock
from api.routes.tool import McpToolConfig, _populate_discovered_tools
def test_mcp_config_accepts_discovered_tools():
cfg = McpToolConfig(
@ -296,10 +302,10 @@ def test_mcp_config_accepts_discovered_tools():
@pytest.mark.asyncio
async def test_populate_discovered_tools_overwrites_cache(monkeypatch):
import api.routes.tool as tool_mod
import api.services.tool_management as tool_svc
monkeypatch.setattr(
tool_mod,
tool_svc,
"discover_mcp_tools",
AsyncMock(return_value=[{"name": "echo", "description": "Echo"}]),
)
@ -327,10 +333,10 @@ async def test_populate_discovered_tools_non_mcp_is_noop():
@pytest.mark.asyncio
async def test_populate_discovered_tools_server_down_sets_empty(monkeypatch):
import api.routes.tool as tool_mod
import api.services.tool_management as tool_svc
monkeypatch.setattr(
tool_mod,
tool_svc,
"discover_mcp_tools",
AsyncMock(side_effect=RuntimeError("connection refused")),
)
@ -345,10 +351,6 @@ async def test_populate_discovered_tools_server_down_sets_empty(monkeypatch):
# ── Task 7: POST /{tool_uuid}/mcp/refresh ─────────────────────────────────────
from fastapi import HTTPException
from api.routes.tool import refresh_mcp_tools
def _fake_user(org_id=1):
u = MagicMock()
@ -373,19 +375,19 @@ def _mcp_tool_model(org_id=1):
@pytest.mark.asyncio
async def test_refresh_success(monkeypatch):
import api.routes.tool as tool_mod
import api.services.tool_management as tool_svc
tool = _mcp_tool_model()
monkeypatch.setattr(
tool_mod.db_client, "get_tool_by_uuid", AsyncMock(return_value=tool)
tool_svc.db_client, "get_tool_by_uuid", AsyncMock(return_value=tool)
)
monkeypatch.setattr(
tool_mod.db_client,
tool_svc.db_client,
"update_tool",
AsyncMock(return_value=tool),
)
monkeypatch.setattr(
tool_mod,
tool_svc,
"discover_mcp_tools",
AsyncMock(return_value=[{"name": "echo", "description": "Echo"}]),
)
@ -396,29 +398,29 @@ async def test_refresh_success(monkeypatch):
@pytest.mark.asyncio
async def test_refresh_server_down_returns_200_with_error(monkeypatch):
import api.routes.tool as tool_mod
import api.services.tool_management as tool_svc
tool = _mcp_tool_model()
monkeypatch.setattr(
tool_mod.db_client, "get_tool_by_uuid", AsyncMock(return_value=tool)
tool_svc.db_client, "get_tool_by_uuid", AsyncMock(return_value=tool)
)
monkeypatch.setattr(tool_mod.db_client, "update_tool", AsyncMock(return_value=tool))
monkeypatch.setattr(tool_mod, "discover_mcp_tools", AsyncMock(return_value=[]))
monkeypatch.setattr(tool_svc.db_client, "update_tool", AsyncMock(return_value=tool))
monkeypatch.setattr(tool_svc, "discover_mcp_tools", AsyncMock(return_value=[]))
resp = await refresh_mcp_tools("tu-mcp", user=_fake_user())
assert resp.discovered_tools == []
assert resp.error # non-empty human-readable message
# update_tool should NOT be called when discovery returns empty
tool_mod.db_client.update_tool.assert_not_called()
tool_svc.db_client.update_tool.assert_not_called()
@pytest.mark.asyncio
async def test_refresh_non_mcp_is_400(monkeypatch):
import api.routes.tool as tool_mod
import api.services.tool_management as tool_svc
tool = _mcp_tool_model()
tool.category = "http_api"
monkeypatch.setattr(
tool_mod.db_client, "get_tool_by_uuid", AsyncMock(return_value=tool)
tool_svc.db_client, "get_tool_by_uuid", AsyncMock(return_value=tool)
)
with pytest.raises(HTTPException) as ei:
await refresh_mcp_tools("tu-mcp", user=_fake_user())
@ -427,10 +429,10 @@ async def test_refresh_non_mcp_is_400(monkeypatch):
@pytest.mark.asyncio
async def test_refresh_not_found_is_404(monkeypatch):
import api.routes.tool as tool_mod
import api.services.tool_management as tool_svc
monkeypatch.setattr(
tool_mod.db_client, "get_tool_by_uuid", AsyncMock(return_value=None)
tool_svc.db_client, "get_tool_by_uuid", AsyncMock(return_value=None)
)
with pytest.raises(HTTPException) as ei:
await refresh_mcp_tools("nope", user=_fake_user())