|
|
@@ -378,6 +378,163 @@ export class McpEndpoint extends BaseEndpointComponent {
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+ ),
|
|
|
+
|
|
|
+ // MCP Stats
|
|
|
+ this.createEndpoint(
|
|
|
+ 'GET',
|
|
|
+ '/api/mcp/stats',
|
|
|
+ this.getMcpStats.bind(this),
|
|
|
+ {
|
|
|
+ summary: 'Get MCP statistics',
|
|
|
+ description: 'Get overall MCP statistics including top tools and recent activity',
|
|
|
+ tags: ['MCP'],
|
|
|
+ requiresAuth: true,
|
|
|
+ responses: {
|
|
|
+ '200': {
|
|
|
+ description: 'MCP statistics',
|
|
|
+ schema: {
|
|
|
+ type: 'object',
|
|
|
+ properties: {
|
|
|
+ total_calls: { type: 'number' },
|
|
|
+ successful_calls: { type: 'number' },
|
|
|
+ failed_calls: { type: 'number' },
|
|
|
+ average_response_time: { type: 'number' },
|
|
|
+ top_tools: {
|
|
|
+ type: 'array',
|
|
|
+ items: {
|
|
|
+ type: 'object',
|
|
|
+ properties: {
|
|
|
+ tool_name: { type: 'string' },
|
|
|
+ call_count: { type: 'number' }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ recent_activity: {
|
|
|
+ type: 'array',
|
|
|
+ items: {
|
|
|
+ type: 'object',
|
|
|
+ properties: {
|
|
|
+ id: { type: 'string' },
|
|
|
+ shop_id: { type: 'string' },
|
|
|
+ tool_name: { type: 'string' },
|
|
|
+ query: { type: 'string' },
|
|
|
+ success: { type: 'boolean' },
|
|
|
+ response_time: { type: 'number' },
|
|
|
+ error_message: { type: 'string', nullable: true },
|
|
|
+ created_at: { type: 'string' }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ),
|
|
|
+
|
|
|
+ // Shop MCP Logs
|
|
|
+ this.createEndpoint(
|
|
|
+ 'GET',
|
|
|
+ '/api/mcp/shops/:shopId/logs',
|
|
|
+ this.getShopMcpLogs.bind(this),
|
|
|
+ {
|
|
|
+ summary: 'Get shop MCP logs',
|
|
|
+ description: 'Get MCP logs for a specific shop',
|
|
|
+ tags: ['MCP'],
|
|
|
+ requiresAuth: true,
|
|
|
+ parameters: [
|
|
|
+ {
|
|
|
+ name: 'shopId',
|
|
|
+ in: 'path',
|
|
|
+ type: 'string',
|
|
|
+ required: true,
|
|
|
+ description: 'Shop ID'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: 'limit',
|
|
|
+ in: 'query',
|
|
|
+ type: 'number',
|
|
|
+ required: false,
|
|
|
+ description: 'Maximum number of logs to return'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: 'success',
|
|
|
+ in: 'query',
|
|
|
+ type: 'boolean',
|
|
|
+ required: false,
|
|
|
+ description: 'Filter by success status'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: 'tool_name',
|
|
|
+ in: 'query',
|
|
|
+ type: 'string',
|
|
|
+ required: false,
|
|
|
+ description: 'Filter by tool name'
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ responses: {
|
|
|
+ '200': {
|
|
|
+ description: 'Shop MCP logs',
|
|
|
+ schema: {
|
|
|
+ type: 'object',
|
|
|
+ properties: {
|
|
|
+ logs: {
|
|
|
+ type: 'array',
|
|
|
+ items: {
|
|
|
+ type: 'object',
|
|
|
+ properties: {
|
|
|
+ id: { type: 'string' },
|
|
|
+ shop_id: { type: 'string' },
|
|
|
+ tool_name: { type: 'string' },
|
|
|
+ query: { type: 'string' },
|
|
|
+ success: { type: 'boolean' },
|
|
|
+ response_time: { type: 'number' },
|
|
|
+ error_message: { type: 'string', nullable: true },
|
|
|
+ created_at: { type: 'string' }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ '404': { description: 'Shop not found' }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ),
|
|
|
+
|
|
|
+ // Clear Shop MCP Logs
|
|
|
+ this.createEndpoint(
|
|
|
+ 'DELETE',
|
|
|
+ '/api/mcp/shops/:shopId/logs',
|
|
|
+ this.clearShopMcpLogs.bind(this),
|
|
|
+ {
|
|
|
+ summary: 'Clear shop MCP logs',
|
|
|
+ description: 'Delete all MCP logs for a specific shop',
|
|
|
+ tags: ['MCP'],
|
|
|
+ requiresAuth: true,
|
|
|
+ parameters: [
|
|
|
+ {
|
|
|
+ name: 'shopId',
|
|
|
+ in: 'path',
|
|
|
+ type: 'string',
|
|
|
+ required: true,
|
|
|
+ description: 'Shop ID'
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ responses: {
|
|
|
+ '200': {
|
|
|
+ description: 'Logs cleared successfully',
|
|
|
+ schema: {
|
|
|
+ type: 'object',
|
|
|
+ properties: {
|
|
|
+ message: { type: 'string' }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ '404': { description: 'Shop not found' }
|
|
|
+ }
|
|
|
+ }
|
|
|
)
|
|
|
];
|
|
|
}
|
|
|
@@ -535,4 +692,89 @@ export class McpEndpoint extends BaseEndpointComponent {
|
|
|
res.status(500).json({ error: 'Internal server error' });
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ private async getMcpStats(req: Request, res: Response): Promise<void> {
|
|
|
+ try {
|
|
|
+ // Get overall analytics
|
|
|
+ const analytics = this.db.getMcpAnalytics({});
|
|
|
+
|
|
|
+ // Get top tools by usage
|
|
|
+ const toolUsage = this.db.getMcpToolUsage({});
|
|
|
+ const topTools = Object.entries(toolUsage)
|
|
|
+ .map(([tool_name, call_count]) => ({ tool_name, call_count }))
|
|
|
+ .sort((a, b) => b.call_count - a.call_count)
|
|
|
+ .slice(0, 10);
|
|
|
+
|
|
|
+ // Get recent activity (last 20 logs)
|
|
|
+ const recentLogs = this.db.getMcpLogs({ limit: 20, offset: 0 });
|
|
|
+
|
|
|
+ res.json({
|
|
|
+ total_calls: analytics.total_calls,
|
|
|
+ successful_calls: analytics.successful_calls,
|
|
|
+ failed_calls: analytics.failed_calls,
|
|
|
+ average_response_time: analytics.avg_response_time,
|
|
|
+ top_tools: topTools,
|
|
|
+ recent_activity: recentLogs.logs
|
|
|
+ });
|
|
|
+ } catch (error) {
|
|
|
+ logger.error('Failed to get MCP stats:', error);
|
|
|
+ res.status(500).json({ error: 'Internal server error' });
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private async getShopMcpLogs(req: Request, res: Response): Promise<void> {
|
|
|
+ try {
|
|
|
+ const { shopId } = req.params;
|
|
|
+ const { limit, success, tool_name } = req.query;
|
|
|
+
|
|
|
+ // Get shop from database - try both internal ID and custom ID
|
|
|
+ let shop = this.db.getShopById(shopId);
|
|
|
+ if (!shop) {
|
|
|
+ shop = this.db.getShopByCustomId(shopId);
|
|
|
+ }
|
|
|
+ if (!shop) {
|
|
|
+ res.status(404).json({ error: 'Shop not found' });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const options: any = {
|
|
|
+ shopId: shop.id,
|
|
|
+ limit: parseInt(limit as string) || 100,
|
|
|
+ offset: 0
|
|
|
+ };
|
|
|
+
|
|
|
+ if (success !== undefined) options.success = success === 'true';
|
|
|
+ if (tool_name) options.toolName = tool_name as string;
|
|
|
+
|
|
|
+ const result = this.db.getMcpLogs(options);
|
|
|
+
|
|
|
+ res.json({ logs: result.logs });
|
|
|
+ } catch (error) {
|
|
|
+ logger.error('Failed to get shop MCP logs:', error);
|
|
|
+ res.status(500).json({ error: 'Internal server error' });
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private async clearShopMcpLogs(req: Request, res: Response): Promise<void> {
|
|
|
+ try {
|
|
|
+ const { shopId } = req.params;
|
|
|
+
|
|
|
+ // Get shop from database - try both internal ID and custom ID
|
|
|
+ let shop = this.db.getShopById(shopId);
|
|
|
+ if (!shop) {
|
|
|
+ shop = this.db.getShopByCustomId(shopId);
|
|
|
+ }
|
|
|
+ if (!shop) {
|
|
|
+ res.status(404).json({ error: 'Shop not found' });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.db.clearShopMcpLogs(shop.id);
|
|
|
+
|
|
|
+ res.json({ message: `Cleared all MCP logs for shop ${shop.id}` });
|
|
|
+ } catch (error) {
|
|
|
+ logger.error('Failed to clear shop MCP logs:', error);
|
|
|
+ res.status(500).json({ error: 'Internal server error' });
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|