Merge pull request #17 from Danielpeter-99/main

feat(mcp): enhance MCP server creation with tool discovery and async handling
This commit is contained in:
Davidson Gomes 2025-05-18 08:13:07 -03:00 committed by GitHub
commit ef4e4ee1c7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 70 additions and 5 deletions

View File

@ -28,6 +28,7 @@
"""
from fastapi import APIRouter, Depends, HTTPException, status
from starlette.concurrency import run_in_threadpool
from sqlalchemy.orm import Session
from src.config.database import get_db
from typing import List
@ -54,7 +55,7 @@ router = APIRouter(
responses={404: {"description": "Not found"}},
)
# Last edited by Arley Peter on 2025-05-17
@router.post("/", response_model=MCPServer, status_code=status.HTTP_201_CREATED)
async def create_mcp_server(
server: MCPServerCreate,
@ -64,7 +65,7 @@ async def create_mcp_server(
# Only administrators can create MCP servers
await verify_admin(payload)
return mcp_server_service.create_mcp_server(db, server)
return await run_in_threadpool(mcp_server_service.create_mcp_server, db, server)
@router.get("/", response_model=List[MCPServer])

View File

@ -262,14 +262,14 @@ class ToolConfig(BaseModel):
inputModes: List[str] = Field(default_factory=list)
outputModes: List[str] = Field(default_factory=list)
# Last edited by Arley Peter on 2025-05-17
class MCPServerBase(BaseModel):
name: str
description: Optional[str] = None
config_type: str = Field(default="studio")
config_json: Dict[str, Any] = Field(default_factory=dict)
environments: Dict[str, Any] = Field(default_factory=dict)
tools: List[ToolConfig] = Field(default_factory=list)
tools: Optional[List[ToolConfig]] = Field(default_factory=list)
type: str = Field(default="official")

View File

@ -32,6 +32,7 @@ from sqlalchemy.exc import SQLAlchemyError
from fastapi import HTTPException, status
from src.models.models import MCPServer
from src.schemas.schemas import MCPServerCreate
from src.utils.mcp_discovery import discover_mcp_tools
from typing import List, Optional
import uuid
import logging
@ -72,8 +73,16 @@ def create_mcp_server(db: Session, server: MCPServerCreate) -> MCPServer:
try:
# Convert tools to JSON serializable format
server_data = server.model_dump()
server_data["tools"] = [tool.model_dump() for tool in server.tools]
# Last edited by Arley Peter on 2025-05-17
supplied_tools = server_data.pop("tools", [])
if not supplied_tools:
discovered = discover_mcp_tools(server_data["config_json"])
print(f"🔍 Found {len(discovered)} tools.")
server_data["tools"] = discovered
else:
server_data["tools"] = [tool.model_dump() for tool in supplied_tools]
db_server = MCPServer(**server_data)
db.add(db_server)
db.commit()

View File

@ -0,0 +1,55 @@
"""
@author: Arley Peter
@file: mcp_discovery.py
Developed by: Arley Peter
Creation date: May 05, 2025
@copyright © Evolution API 2025. All rights reserved.
Licensed under the Apache License, Version 2.0
You may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@important
For any future changes to the code in this file, it is recommended to
include, together with the modification, the information of the developer
who changed it and the date of modification.
"""
from typing import List, Dict, Any
import asyncio
async def _discover_async(config_json: Dict[str, Any]) -> List[Dict[str, Any]]:
"""Return a list[dict] with the tool metadata advertised by the MCP server."""
from src.services.mcp_service import MCPService
service = MCPService()
tools, exit_stack = await service._connect_to_mcp_server(config_json)
serialised = [t.to_dict() if hasattr(t, "to_dict") else {
"id": t.name,
"name": t.name,
"description": getattr(t, "description", t.name),
"tags": getattr(t, "tags", []),
"examples": getattr(t, "examples", []),
"inputModes": getattr(t, "input_modes", ["text"]),
"outputModes": getattr(t, "output_modes", ["text"]),
} for t in tools]
if exit_stack:
await exit_stack.aclose()
return serialised
def discover_mcp_tools(config_json: Dict[str, Any]) -> List[Dict[str, Any]]:
"""Sync wrapper so we can call it from a sync service function."""
return asyncio.run(_discover_async(config_json))