Coverage for integrations / social / api_mcp.py: 27.2%
81 statements
« prev ^ index » next coverage.py v7.14.0, created at 2026-05-12 04:49 +0000
« prev ^ index » next coverage.py v7.14.0, created at 2026-05-12 04:49 +0000
1"""
2HevolveSocial - MCP Tool Registry Blueprint
34 endpoints for MCP server/tool discovery and registration.
4Wires to frontend mcpApi in socialApi.js.
5"""
6import logging
7from flask import Blueprint, request, jsonify, g
9from .auth import require_auth, optional_auth
10from .models import get_db, MCPServer, MCPTool
12logger = logging.getLogger('hevolve_social')
14mcp_bp = Blueprint('mcp', __name__, url_prefix='/api/social')
17def _ok(data=None, meta=None, status=200):
18 r = {'success': True}
19 if data is not None:
20 r['data'] = data
21 if meta is not None:
22 r['meta'] = meta
23 return jsonify(r), status
26def _err(msg, status=400):
27 return jsonify({'success': False, 'error': msg}), status
30def _paginate(total, limit, offset):
31 return {'total': total, 'limit': limit, 'offset': offset,
32 'has_more': offset + limit < total}
35def _get_json():
36 return request.get_json(force=True, silent=True) or {}
39# ═══════════════════════════════════════════════════════════════
40# MCP TOOL REGISTRY (4 endpoints)
41# ═══════════════════════════════════════════════════════════════
43@mcp_bp.route('/mcp/servers', methods=['GET'])
44@optional_auth
45def list_mcp_servers():
46 """List registered MCP tool servers."""
47 db = get_db()
48 try:
49 limit = min(int(request.args.get('limit', 20)), 50)
50 offset = int(request.args.get('offset', 0))
51 category = request.args.get('category')
52 q = request.args.get('q', '').strip()
54 query = db.query(MCPServer).filter_by(is_active=True)
55 if category:
56 query = query.filter_by(category=category)
57 if q:
58 query = query.filter(
59 MCPServer.name.ilike(f'%{q}%') |
60 MCPServer.description.ilike(f'%{q}%')
61 )
62 total = query.count()
63 servers = query.order_by(MCPServer.created_at.desc()).offset(offset).limit(limit).all()
64 return _ok([s.to_dict() for s in servers], _paginate(total, limit, offset))
65 except Exception as e:
66 logger.error(f"mcp/servers GET error: {e}")
67 return _err(str(e), 500)
68 finally:
69 db.close()
72@mcp_bp.route('/mcp/servers/<server_id>/tools', methods=['GET'])
73@optional_auth
74def list_mcp_tools(server_id):
75 """List tools for a specific MCP server."""
76 db = get_db()
77 try:
78 server = db.query(MCPServer).filter_by(id=server_id, is_active=True).first()
79 if not server:
80 return _err('Server not found', 404)
81 tools = db.query(MCPTool).filter_by(server_id=server_id).all()
82 return _ok({
83 'server': server.to_dict(),
84 'tools': [t.to_dict() for t in tools],
85 })
86 except Exception as e:
87 logger.error(f"mcp/servers/{server_id}/tools error: {e}")
88 return _err(str(e), 500)
89 finally:
90 db.close()
93@mcp_bp.route('/mcp/register', methods=['POST'])
94@require_auth
95def register_mcp_server():
96 """Register a new MCP tool server with its tools."""
97 db = get_db()
98 try:
99 data = _get_json()
100 name = data.get('name', '').strip()
101 if not name:
102 return _err('name required')
104 server = MCPServer(
105 owner_id=g.user_id,
106 name=name,
107 description=data.get('description', ''),
108 url=data.get('url', ''),
109 category=data.get('category', 'general'),
110 )
111 db.add(server)
112 db.flush() # get server.id
114 # Register tools if provided
115 tools_data = data.get('tools', [])
116 for td in tools_data[:50]: # cap at 50 tools per server
117 tool = MCPTool(
118 server_id=server.id,
119 name=td.get('name', 'unnamed'),
120 description=td.get('description', ''),
121 input_schema=td.get('input_schema', {}),
122 )
123 db.add(tool)
125 db.commit()
126 db.refresh(server)
127 return _ok(server.to_dict(), status=201)
128 except Exception as e:
129 db.rollback()
130 logger.error(f"mcp/register POST error: {e}")
131 return _err(str(e), 500)
132 finally:
133 db.close()
136@mcp_bp.route('/mcp/discover', methods=['GET'])
137@optional_auth
138def discover_mcp():
139 """Discover MCP servers and tools by search query."""
140 db = get_db()
141 try:
142 q = request.args.get('q', '').strip()
143 limit = min(int(request.args.get('limit', 20)), 50)
145 if not q:
146 return _err('q parameter required')
148 # Search servers
149 servers = db.query(MCPServer).filter(
150 MCPServer.is_active == True,
151 MCPServer.name.ilike(f'%{q}%') |
152 MCPServer.description.ilike(f'%{q}%')
153 ).limit(limit).all()
155 # Search tools
156 tools = db.query(MCPTool).filter(
157 MCPTool.name.ilike(f'%{q}%') |
158 MCPTool.description.ilike(f'%{q}%')
159 ).limit(limit).all()
161 return _ok({
162 'servers': [s.to_dict() for s in servers],
163 'tools': [t.to_dict() for t in tools],
164 })
165 except Exception as e:
166 logger.error(f"mcp/discover GET error: {e}")
167 return _err(str(e), 500)
168 finally:
169 db.close()