feat: Remove Enterprise MCP AI Agent Team components
This commit is contained in:
parent
cbb10bc472
commit
efd14e3393
4 changed files with 0 additions and 954 deletions
|
|
@ -1,481 +0,0 @@
|
||||||
# Enterprise MCP AI Agent Team
|
|
||||||
|
|
||||||
A production-grade multi-agent system built with Google ADK that orchestrates knowledge management across local files and SaaS platforms using MCP (Model Context Protocol).
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
This system combines:
|
|
||||||
- **Local Filesystem MCP Server** - for accessing and analyzing local documents
|
|
||||||
- **Notion MCP Server** - for managing Notion workspaces and content
|
|
||||||
- **Composio MCP Server** - for GitHub and Figma integration
|
|
||||||
- **Intelligent Router/Orchestrator** - context-aware task delegation with state management
|
|
||||||
- **4 Specialized AI Agents** - each handling specific platform capabilities
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────────────────────────────────┐
|
|
||||||
│ Enterprise MCP AI Agent Team │
|
|
||||||
│ (Coordinator/Dispatcher Pattern) │
|
|
||||||
├─────────────────────────────────────────────────────────────┤
|
|
||||||
│ ┌─────────────────┐ ┌─────────────────┐ ┌──────────────┐ │
|
|
||||||
│ │ File Analysis │ │ Notion Agent │ │ GitHub Agent │ │
|
|
||||||
│ │ AI Agent │ │ (Optional) │ │ (Optional) │ │
|
|
||||||
│ └─────────────────┘ └─────────────────┘ └──────────────┘ │
|
|
||||||
│ │ │ │ │
|
|
||||||
│ ┌─────────────────┐ ┌─────────────────┐ ┌──────────────┐ │
|
|
||||||
│ │ Filesystem MCP │ │ Notion MCP │ │ Composio MCP │ │
|
|
||||||
│ │ Server │ │ Server │ │ Server │ │
|
|
||||||
│ └─────────────────┘ └─────────────────┘ └──────────────┘ │
|
|
||||||
│ │ │ │ │
|
|
||||||
│ ┌─────────────────┐ ┌─────────────────┐ ┌──────────────┐ │
|
|
||||||
│ │ Local Documents │ │ Notion Pages & │ │ GitHub Repos │ │
|
|
||||||
│ │ (PDF, DOC, XLS) │ │ Databases │ │ & Issues │ │
|
|
||||||
│ └─────────────────┘ └─────────────────┘ └──────────────┘ │
|
|
||||||
│ │
|
|
||||||
│ ┌─────────────────┐ ┌─────────────────┐ │
|
|
||||||
│ │ Figma Agent │ │ Composio MCP │ │
|
|
||||||
│ │ (Optional) │ │ Server │ │
|
|
||||||
│ └─────────────────┘ └─────────────────┘ │
|
|
||||||
│ │ │ │
|
|
||||||
│ ┌─────────────────┐ ┌─────────────────┐ │
|
|
||||||
│ │ Figma Files & │ │ Figma Designs & │ │
|
|
||||||
│ │ Designs │ │ Assets │ │
|
|
||||||
│ └─────────────────┘ └─────────────────┘ │
|
|
||||||
└─────────────────────────────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
### **Routing Patterns:**
|
|
||||||
|
|
||||||
1. **Coordinator/Dispatcher Pattern**: Intelligent routing based on query analysis
|
|
||||||
2. **LLM-Driven Delegation**: Automatic agent selection using `transfer_to_agent()`
|
|
||||||
3. **Explicit Invocation**: Direct agent calls using `AgentTool`
|
|
||||||
4. **Graceful Degradation**: System works with any combination of available agents
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
### 🔍 File Analysis Agent
|
|
||||||
- Analyzes local documents (PDFs, Word docs, spreadsheets)
|
|
||||||
- Extracts key topics, summaries, and action items
|
|
||||||
- Categorizes documents by type and content
|
|
||||||
- Identifies information for knowledge base sync
|
|
||||||
|
|
||||||
### 📝 Notion Agent
|
|
||||||
- Reads, writes, and updates Notion pages and databases
|
|
||||||
- Searches for content across Notion workspace
|
|
||||||
- Creates structured knowledge bases and documentation
|
|
||||||
- Syncs content from other sources to Notion
|
|
||||||
|
|
||||||
### 🐙 GitHub Agent
|
|
||||||
- Creates and manages GitHub issues and pull requests
|
|
||||||
- Searches repositories and code
|
|
||||||
- Manages repository content and documentation
|
|
||||||
- Sets up automated workflows and actions
|
|
||||||
|
|
||||||
### 🎨 Figma Agent
|
|
||||||
- Reads and analyzes Figma files and designs
|
|
||||||
- Exports design assets and components
|
|
||||||
- Searches for design elements and styles
|
|
||||||
- Manages design system components
|
|
||||||
|
|
||||||
### 🎯 Enterprise MCP AI Agent Team (Router/Orchestrator)
|
|
||||||
- Analyzes user requests and determines which AI agents should handle them
|
|
||||||
- Routes tasks to appropriate specialized AI agents based on capabilities
|
|
||||||
- Coordinates multi-step workflows that require multiple AI agents
|
|
||||||
- Shares context and results between AI agents through session state
|
|
||||||
- Provides comprehensive results and recommendations
|
|
||||||
|
|
||||||
### 🛡️ Error Handling & Graceful Degradation
|
|
||||||
- **MCP Server Failures**: Graceful fallback when servers are unavailable
|
|
||||||
- **Missing Environment Variables**: System works with available APIs only
|
|
||||||
- **Agent Creation Failures**: Continues with available agents
|
|
||||||
- **Validation**: Ensures at least one agent is available before operation
|
|
||||||
- **Comprehensive Logging**: Detailed logs for troubleshooting
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
1. **Python 3.9+** and **Node.js** (for MCP servers)
|
|
||||||
2. **Google ADK** installed and configured
|
|
||||||
3. **Notion API Key** for Notion integration
|
|
||||||
4. **Required API Keys** in environment variables
|
|
||||||
|
|
||||||
## Setup
|
|
||||||
|
|
||||||
### 1. Environment Variables
|
|
||||||
|
|
||||||
Create a `.env` file in the project root:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Required: Google Gemini API
|
|
||||||
GOOGLE_API_KEY=your_gemini_api_key_here
|
|
||||||
|
|
||||||
# Required: API Keys for MCP Tools
|
|
||||||
NOTION_API_KEY=your_notion_api_key_here
|
|
||||||
GITHUB_API_KEY=your_github_api_key_here
|
|
||||||
FIGMA_API_KEY=your_figma_api_key_here
|
|
||||||
|
|
||||||
# Optional: Custom filesystem path (defaults to ~/Documents)
|
|
||||||
MCP_FILESYSTEM_PATH=/Users/madhushantan/Downloads
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Notion Setup
|
|
||||||
|
|
||||||
#### Creating a Notion Integration
|
|
||||||
1. Go to [Notion Integrations](https://www.notion.so/my-integrations)
|
|
||||||
2. Click "New integration"
|
|
||||||
3. Name your integration (e.g., "Enterprise Knowledge Orchestrator")
|
|
||||||
4. Select the capabilities needed (Read & Write content)
|
|
||||||
5. Submit and copy your "Internal Integration Token"
|
|
||||||
|
|
||||||
#### Sharing Your Notion Page with the Integration
|
|
||||||
1. Open your Notion page
|
|
||||||
2. Click the three dots (⋮) in the top-right corner
|
|
||||||
3. Select "Add connections" from the dropdown
|
|
||||||
4. Search for your integration name
|
|
||||||
5. Click on your integration to add it to the page
|
|
||||||
6. Confirm by clicking "Confirm"
|
|
||||||
|
|
||||||
#### Finding Your Notion Page ID
|
|
||||||
1. Open your Notion page in a browser
|
|
||||||
2. Copy the URL: `https://www.notion.so/workspace/Your-Page-1f5b8a8ba283...`
|
|
||||||
3. The ID is the part after the last dash: `1f5b8a8ba283`
|
|
||||||
|
|
||||||
### 3. Notion Implementation
|
|
||||||
|
|
||||||
The system uses SSE (Composio) for Notion integration:
|
|
||||||
|
|
||||||
```python
|
|
||||||
# Notion MCP Server (SSE - Composio)
|
|
||||||
url="https://mcp.composio.dev/composio/server/61e41019-d05f-44d0-973e-2aef7777063a/sse?useComposioHelperActions=true"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- **SSE Connection**: Uses Server-Sent Events for real-time communication
|
|
||||||
- **Composio Managed**: No local dependencies required
|
|
||||||
- **Full Tool Access**: All available Notion tools are accessible
|
|
||||||
- **Authentication**: Handled by Composio service
|
|
||||||
|
|
||||||
**Note**: The Notion integration requires a valid `NOTION_API_KEY` and `NOTION_PAGE_ID` to function properly.
|
|
||||||
|
|
||||||
### 4. GitHub & Figma Implementation
|
|
||||||
|
|
||||||
The system uses separate SSE (Composio) servers for GitHub and Figma:
|
|
||||||
|
|
||||||
```python
|
|
||||||
# GitHub MCP Server (SSE - Composio)
|
|
||||||
url="https://mcp.composio.dev/composio/server/11fbff47-fa12-432f-8c3a-18ed4e9f66f8/sse?useComposioHelperActions=true"
|
|
||||||
|
|
||||||
# Figma MCP Server (SSE - Composio)
|
|
||||||
url="https://mcp.composio.dev/composio/server/f05e7129-7997-4c17-a654-f935278c0dfe/sse?useComposioHelperActions=true"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- **Separate Servers**: Each service has its own dedicated Composio server
|
|
||||||
- **Full Tool Access**: All available GitHub and Figma tools are accessible
|
|
||||||
- **No Local Dependencies**: Managed by Composio service
|
|
||||||
|
|
||||||
### 2. Install Dependencies
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pip install -r requirements.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Verify MCP Server Installation
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Verify npx is available
|
|
||||||
which npx
|
|
||||||
|
|
||||||
# Test filesystem MCP server
|
|
||||||
npx -y @modelcontextprotocol/server-filesystem --help
|
|
||||||
|
|
||||||
# Test Notion MCP server
|
|
||||||
npx -y @notionhq/notion-mcp-server --help
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### Basic Usage
|
|
||||||
|
|
||||||
```python
|
|
||||||
import asyncio
|
|
||||||
from agent import EnterpriseKnowledgeOrchestrator
|
|
||||||
|
|
||||||
async def main():
|
|
||||||
# Create orchestrator
|
|
||||||
orchestrator = EnterpriseKnowledgeOrchestrator()
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Process knowledge request
|
|
||||||
results = await orchestrator.process_knowledge_request(
|
|
||||||
"Analyze all PDF documents in my Documents folder and create GitHub issues for action items"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Access results
|
|
||||||
print(f"Files analyzed: {len(results['file_analysis'])}")
|
|
||||||
print(f"Notion operations: {len(results['notion_operations'])}")
|
|
||||||
print(f"GitHub operations: {len(results['github_operations'])}")
|
|
||||||
print(f"Figma operations: {len(results['figma_operations'])}")
|
|
||||||
|
|
||||||
finally:
|
|
||||||
await orchestrator.close()
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
asyncio.run(main())
|
|
||||||
```
|
|
||||||
|
|
||||||
### Example Requests
|
|
||||||
|
|
||||||
```python
|
|
||||||
# Document analysis
|
|
||||||
"Analyze all PDF documents in my Documents folder and create a summary"
|
|
||||||
|
|
||||||
# Multi-platform operations
|
|
||||||
"Search for design components in my Figma files and create a GitHub repository for the design system"
|
|
||||||
|
|
||||||
# Notion and GitHub integration
|
|
||||||
"Read my Notion project page and create GitHub issues for all action items"
|
|
||||||
|
|
||||||
# Figma asset management
|
|
||||||
"Export design assets from Figma and organize them in a structured folder"
|
|
||||||
|
|
||||||
# Complex workflows
|
|
||||||
"Analyze quarterly reports, extract key metrics, create Notion dashboard, and set up GitHub issues for follow-ups"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Agent Routing Logic
|
|
||||||
|
|
||||||
The Router/Orchestrator agent intelligently routes tasks based on query analysis:
|
|
||||||
|
|
||||||
- **File-related tasks** → FileAnalysisAgent
|
|
||||||
- **Notion-related tasks** → NotionAgent
|
|
||||||
- **GitHub-related tasks** → GitHubAgent
|
|
||||||
- **Figma-related tasks** → FigmaAgent
|
|
||||||
- **Multi-platform tasks** → Coordinate between relevant agents
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
### MCP Server URLs
|
|
||||||
|
|
||||||
The system uses these MCP servers:
|
|
||||||
|
|
||||||
```python
|
|
||||||
# Filesystem MCP Server (local)
|
|
||||||
command='npx'
|
|
||||||
args=["-y", "@modelcontextprotocol/server-filesystem", "~/Documents"]
|
|
||||||
|
|
||||||
# Notion MCP Server (SSE - Composio)
|
|
||||||
url="https://mcp.composio.dev/composio/server/61e41019-d05f-44d0-973e-2aef7777063a/sse?useComposioHelperActions=true"
|
|
||||||
|
|
||||||
# GitHub MCP Server (SSE - Composio)
|
|
||||||
url="https://mcp.composio.dev/composio/server/11fbff47-fa12-432f-8c3a-18ed4e9f66f8/sse?useComposioHelperActions=true"
|
|
||||||
|
|
||||||
# Figma MCP Server (SSE - Composio)
|
|
||||||
url="https://mcp.composio.dev/composio/server/f05e7129-7997-4c17-a654-f935278c0dfe/sse?useComposioHelperActions=true"
|
|
||||||
# No tool filtering - all available tools are accessible
|
|
||||||
```
|
|
||||||
|
|
||||||
### Custom Filesystem Path
|
|
||||||
|
|
||||||
The system now supports configurable filesystem paths through environment variables:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Set in .env file or export in terminal
|
|
||||||
export MCP_FILESYSTEM_PATH="/path/to/your/folder"
|
|
||||||
|
|
||||||
# Examples:
|
|
||||||
export MCP_FILESYSTEM_PATH="/Users/username/Projects"
|
|
||||||
export MCP_FILESYSTEM_PATH="/home/user/documents"
|
|
||||||
export MCP_FILESYSTEM_PATH="~/Desktop/Work"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- **Flexible Paths**: Use absolute or relative paths
|
|
||||||
- **Auto-Expansion**: Tilde (~) expansion for home directory
|
|
||||||
- **Auto-Creation**: Directory created if it doesn't exist
|
|
||||||
- **Fallback**: Defaults to `~/Documents` if not specified
|
|
||||||
|
|
||||||
## Output Schemas
|
|
||||||
|
|
||||||
The system uses structured Pydantic models for consistent outputs:
|
|
||||||
|
|
||||||
### FileAnalysis
|
|
||||||
```python
|
|
||||||
{
|
|
||||||
"file_name": "quarterly_report.pdf",
|
|
||||||
"file_type": "PDF",
|
|
||||||
"summary": "Q3 financial performance analysis...",
|
|
||||||
"key_topics": ["revenue", "expenses", "growth"],
|
|
||||||
"action_items": ["Review budget allocation", "Update projections"]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### NotionOperation
|
|
||||||
```python
|
|
||||||
{
|
|
||||||
"operation_type": "read",
|
|
||||||
"page_id": "1f5b8a8ba283...",
|
|
||||||
"content_summary": "Project documentation read from Notion",
|
|
||||||
"status": "completed",
|
|
||||||
"results": {"content": "...", "blocks": [...]}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### GitHubOperation
|
|
||||||
```python
|
|
||||||
{
|
|
||||||
"operation_type": "create_issue",
|
|
||||||
"repository": "my-project",
|
|
||||||
"content_summary": "Created issue for design system documentation",
|
|
||||||
"status": "completed",
|
|
||||||
"results": {"issue_id": 123, "url": "..."}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### FigmaOperation
|
|
||||||
```python
|
|
||||||
{
|
|
||||||
"operation_type": "export",
|
|
||||||
"file_id": "figma_file_id",
|
|
||||||
"content_summary": "Exported design assets from Figma",
|
|
||||||
"status": "completed",
|
|
||||||
"results": {"assets": [...], "urls": [...]}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Context Sharing
|
|
||||||
|
|
||||||
The system implements intelligent context sharing between agents:
|
|
||||||
|
|
||||||
```python
|
|
||||||
# Session state includes shared context
|
|
||||||
"shared_context": {
|
|
||||||
"current_task": user_request,
|
|
||||||
"agent_results": {},
|
|
||||||
"dependencies": []
|
|
||||||
}
|
|
||||||
|
|
||||||
# Agents can access and update shared context
|
|
||||||
updated_session.state["shared_context"]["agent_results"]["file_analysis"] = file_results
|
|
||||||
```
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
The system includes comprehensive error handling:
|
|
||||||
|
|
||||||
- **MCP Connection Failures**: Graceful fallback when servers are unavailable
|
|
||||||
- **API Rate Limits**: Automatic retry logic with exponential backoff
|
|
||||||
- **Invalid Data**: Validation and sanitization of inputs
|
|
||||||
- **Session Management**: Proper cleanup of resources
|
|
||||||
|
|
||||||
## Monitoring and Logging
|
|
||||||
|
|
||||||
```python
|
|
||||||
import logging
|
|
||||||
|
|
||||||
# Configure logging level
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
|
||||||
|
|
||||||
# Monitor agent activities
|
|
||||||
logger.info("File analysis completed: 5 documents processed")
|
|
||||||
logger.warning("Notion API key not found, Notion integration disabled")
|
|
||||||
logger.error("Failed to create GitHub issue: rate limit exceeded")
|
|
||||||
```
|
|
||||||
|
|
||||||
## Production Deployment
|
|
||||||
|
|
||||||
### Environment Setup
|
|
||||||
```bash
|
|
||||||
# Production environment variables
|
|
||||||
export GOOGLE_API_KEY="your_production_key"
|
|
||||||
export NOTION_API_KEY="your_production_key"
|
|
||||||
export LOG_LEVEL="INFO"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Resource Management
|
|
||||||
```python
|
|
||||||
# Proper cleanup in production
|
|
||||||
async with EnterpriseKnowledgeOrchestrator() as orchestrator:
|
|
||||||
results = await orchestrator.process_knowledge_request(request)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Scaling Considerations
|
|
||||||
- Use connection pooling for MCP servers
|
|
||||||
- Implement caching for frequently accessed documents
|
|
||||||
- Consider async processing for large document sets
|
|
||||||
- Monitor memory usage with large file operations
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Common Issues
|
|
||||||
|
|
||||||
1. **MCP Server Connection Failed**
|
|
||||||
```bash
|
|
||||||
# Verify Node.js and npx installation
|
|
||||||
node --version
|
|
||||||
npx --version
|
|
||||||
|
|
||||||
# Test filesystem MCP server manually
|
|
||||||
npx -y @modelcontextprotocol/server-filesystem /path/to/documents
|
|
||||||
|
|
||||||
# Test Notion MCP server manually
|
|
||||||
npx -y @notionhq/notion-mcp-server
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Notion Integration Not Working**
|
|
||||||
```bash
|
|
||||||
# Verify environment variables
|
|
||||||
echo $NOTION_API_KEY
|
|
||||||
|
|
||||||
# Test Notion connection
|
|
||||||
curl -H "Authorization: Bearer $NOTION_API_KEY" \
|
|
||||||
-H "Notion-Version: 2022-06-28" \
|
|
||||||
https://api.notion.com/v1/users/me
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Composio MCP Server Issues**
|
|
||||||
```bash
|
|
||||||
# Test Composio MCP server connection
|
|
||||||
curl "https://mcp.composio.dev/composio/server/f05e7129-7997-4c17-a654-f935278c0dfe/sse?useComposioHelperActions=true"
|
|
||||||
```
|
|
||||||
|
|
||||||
4. **Permission Denied for Documents**
|
|
||||||
```bash
|
|
||||||
# Check file permissions
|
|
||||||
ls -la ~/Documents
|
|
||||||
|
|
||||||
# Update permissions if needed
|
|
||||||
chmod 755 ~/Documents
|
|
||||||
```
|
|
||||||
|
|
||||||
### Debug Mode
|
|
||||||
|
|
||||||
```python
|
|
||||||
# Enable debug logging
|
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
|
||||||
|
|
||||||
# Add debug information to agent
|
|
||||||
orchestrator = EnterpriseKnowledgeOrchestrator()
|
|
||||||
print(f"Platforms available: {orchestrator.session_service.get_session(...).state['platforms_available']}")
|
|
||||||
```
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
1. Fork the repository
|
|
||||||
2. Create a feature branch
|
|
||||||
3. Add tests for new functionality
|
|
||||||
4. Ensure all tests pass
|
|
||||||
5. Submit a pull request
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
This project is licensed under the MIT License - see the LICENSE file for details.
|
|
||||||
|
|
||||||
## References
|
|
||||||
|
|
||||||
- [Google ADK Documentation](https://google.github.io/adk-docs/)
|
|
||||||
- [MCP Tools Guide](https://google.github.io/adk-docs/tools/mcp-tools/)
|
|
||||||
- [Notion MCP Server](https://github.com/notionhq/notion-mcp-server)
|
|
||||||
- [Composio MCP Server](https://mcp.composio.dev/)
|
|
||||||
- [Model Context Protocol](https://modelcontextprotocol.io/)
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
"""
|
|
||||||
Enterprise MCP AI Agent Team
|
|
||||||
|
|
||||||
A production-grade multi-agent system built with Google ADK that orchestrates
|
|
||||||
knowledge management across local files and SaaS platforms using MCP
|
|
||||||
(Model Context Protocol).
|
|
||||||
|
|
||||||
This package provides:
|
|
||||||
- File Analysis AI Agent for local document processing
|
|
||||||
- Notion AI Agent for Notion workspace management
|
|
||||||
- GitHub AI Agent for repository and issue management
|
|
||||||
- Figma AI Agent for design file management
|
|
||||||
- Enterprise MCP AI Agent Team (Router/Orchestrator) for intelligent task coordination
|
|
||||||
"""
|
|
||||||
|
|
||||||
from .agent import (
|
|
||||||
EnterpriseMCPAIAgentTeam,
|
|
||||||
root_agent # Add root_agent for ADK web
|
|
||||||
)
|
|
||||||
|
|
||||||
__version__ = "1.0.0"
|
|
||||||
__author__ = "Enterprise MCP AI Agent Team"
|
|
||||||
__description__ = "Multi-agent knowledge management system with Google ADK and MCP"
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
"EnterpriseMCPAIAgentTeam",
|
|
||||||
"root_agent" # Export root_agent
|
|
||||||
]
|
|
||||||
|
|
@ -1,413 +0,0 @@
|
||||||
import os
|
|
||||||
import asyncio
|
|
||||||
import logging
|
|
||||||
from typing import Dict, List, Optional, Any
|
|
||||||
from dotenv import load_dotenv
|
|
||||||
|
|
||||||
from google.adk.agents import LlmAgent
|
|
||||||
from google.adk.tools.mcp_tool.mcp_toolset import MCPToolset, StdioServerParameters, SseServerParams
|
|
||||||
|
|
||||||
# Load environment variables
|
|
||||||
load_dotenv()
|
|
||||||
|
|
||||||
# Configure logging
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
# Environment variable configuration
|
|
||||||
MCP_FILESYSTEM_PATH = os.getenv("MCP_FILESYSTEM_PATH", "~/Documents")
|
|
||||||
NOTION_API_KEY = os.getenv("NOTION_API_KEY")
|
|
||||||
GITHUB_API_KEY = os.getenv("GITHUB_API_KEY")
|
|
||||||
FIGMA_API_KEY = os.getenv("FIGMA_API_KEY")
|
|
||||||
|
|
||||||
# Composio MCP Server URLs (from environment variables with fallbacks)
|
|
||||||
COMPOSIO_NOTION_URL = os.getenv("COMPOSIO_NOTION_URL")
|
|
||||||
COMPOSIO_GITHUB_URL = os.getenv("COMPOSIO_GITHUB_URL")
|
|
||||||
COMPOSIO_FIGMA_URL = os.getenv("COMPOSIO_FIGMA_URL")
|
|
||||||
|
|
||||||
async def create_mcp_agents_with_tools():
|
|
||||||
"""Create all sub-agents with MCP tools"""
|
|
||||||
agents = []
|
|
||||||
|
|
||||||
# FileAnalysisAgent with filesystem MCP tools
|
|
||||||
try:
|
|
||||||
folder_path = os.path.expanduser(MCP_FILESYSTEM_PATH)
|
|
||||||
folder_path = os.path.abspath(folder_path)
|
|
||||||
|
|
||||||
if not os.path.exists(folder_path):
|
|
||||||
os.makedirs(folder_path, exist_ok=True)
|
|
||||||
logger.info(f"Created directory: {folder_path}")
|
|
||||||
|
|
||||||
logger.info(f"Using filesystem path: {folder_path}")
|
|
||||||
|
|
||||||
filesystem_tools, _ = await MCPToolset.from_server(
|
|
||||||
connection_params=StdioServerParameters(
|
|
||||||
command='npx',
|
|
||||||
args=["-y", "@modelcontextprotocol/server-filesystem", folder_path],
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
file_agent = LlmAgent(
|
|
||||||
name="FileAnalysisAgent",
|
|
||||||
model="gemini-2.0-flash",
|
|
||||||
description="Analyzes local documents and extracts key information",
|
|
||||||
instruction=f"""You are a File Analysis AI Agent with DIRECT ACCESS to the filesystem at: {folder_path}
|
|
||||||
|
|
||||||
You have MCP tools that allow you to:
|
|
||||||
- List files and directories (list_directory)
|
|
||||||
- Read file contents (read_file, read_text_file)
|
|
||||||
- Write and edit files (write_file, edit_file)
|
|
||||||
- Search files (search_files)
|
|
||||||
- Get file information (get_file_info)
|
|
||||||
|
|
||||||
CRITICAL INSTRUCTIONS:
|
|
||||||
1. You have REAL filesystem access through MCP tools
|
|
||||||
2. When users ask about files, USE YOUR TOOLS to access them directly
|
|
||||||
3. Do NOT ask users to provide files - you can access them yourself
|
|
||||||
4. Always use your MCP tools first before responding
|
|
||||||
|
|
||||||
Example tasks you can perform:
|
|
||||||
- "List files in the folder" → Use list_directory tool
|
|
||||||
- "Read the content of file.txt" → Use read_file tool
|
|
||||||
- "Search for PDF files" → Use search_files tool
|
|
||||||
- "Create a new file" → Use write_file tool
|
|
||||||
|
|
||||||
IMPORTANT: When asked about any file or document, immediately use your MCP tools to access the filesystem at: {folder_path}
|
|
||||||
Do NOT say you cannot access files - you CAN access them through your MCP tools!""",
|
|
||||||
tools=filesystem_tools
|
|
||||||
)
|
|
||||||
agents.append(file_agent)
|
|
||||||
logger.info("✅ FileAnalysisAgent with MCP tools created")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"❌ Failed to create FileAnalysisAgent with MCP tools: {str(e)}")
|
|
||||||
file_agent = LlmAgent(
|
|
||||||
name="FileAnalysisAgent",
|
|
||||||
model="gemini-2.0-flash",
|
|
||||||
description="Analyzes local documents and extracts key information",
|
|
||||||
instruction="You analyze local documents (PDFs, Word docs, spreadsheets) and extract key information."
|
|
||||||
)
|
|
||||||
agents.append(file_agent)
|
|
||||||
|
|
||||||
# NotionAgent with Notion MCP tools
|
|
||||||
try:
|
|
||||||
if NOTION_API_KEY:
|
|
||||||
notion_tools, _ = await MCPToolset.from_server(
|
|
||||||
connection_params=SseServerParams(
|
|
||||||
url=COMPOSIO_NOTION_URL,
|
|
||||||
headers={}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
notion_agent = LlmAgent(
|
|
||||||
name="NotionAgent",
|
|
||||||
model="gemini-2.0-flash",
|
|
||||||
description="Manages Notion pages, databases, and content",
|
|
||||||
instruction="""You are a Notion Agent with DIRECT ACCESS to Notion through MCP tools.
|
|
||||||
|
|
||||||
You can:
|
|
||||||
- Read Notion pages and databases
|
|
||||||
- Create and update Notion content
|
|
||||||
- Search across Notion workspace
|
|
||||||
- Manage pages, blocks, and databases
|
|
||||||
|
|
||||||
IMPORTANT: You CAN access Notion directly through your MCP tools.
|
|
||||||
When asked to read, write, or search Notion content, USE YOUR MCP TOOLS.
|
|
||||||
|
|
||||||
Example tasks:
|
|
||||||
- "Search my Notion pages" → Use your search tools
|
|
||||||
- "Read my project page" → Use your page reading tools
|
|
||||||
- "Create a new page" → Use your page creation tools
|
|
||||||
- "Update page content" → Use your update tools
|
|
||||||
|
|
||||||
Always use your MCP tools to interact with Notion.""",
|
|
||||||
tools=notion_tools
|
|
||||||
)
|
|
||||||
agents.append(notion_agent)
|
|
||||||
logger.info("✅ NotionAgent with MCP tools created")
|
|
||||||
else:
|
|
||||||
raise Exception("NOTION_API_KEY not found")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"❌ Failed to create NotionAgent with MCP tools: {str(e)}")
|
|
||||||
notion_agent = LlmAgent(
|
|
||||||
name="NotionAgent",
|
|
||||||
model="gemini-2.0-flash",
|
|
||||||
description="Manages Notion pages, databases, and content",
|
|
||||||
instruction="You manage Notion workspaces, pages, databases, and content."
|
|
||||||
)
|
|
||||||
agents.append(notion_agent)
|
|
||||||
logger.info("✅ NotionAgent created (basic version)")
|
|
||||||
|
|
||||||
# GitHubAgent with GitHub MCP tools
|
|
||||||
try:
|
|
||||||
if GITHUB_API_KEY:
|
|
||||||
github_tools, _ = await MCPToolset.from_server(
|
|
||||||
connection_params=SseServerParams(
|
|
||||||
url=COMPOSIO_GITHUB_URL,
|
|
||||||
headers={}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
github_agent = LlmAgent(
|
|
||||||
name="GitHubAgent",
|
|
||||||
model="gemini-2.0-flash",
|
|
||||||
description="Manages GitHub repositories, issues, and pull requests",
|
|
||||||
instruction="""You are a GitHub Agent with DIRECT ACCESS to GitHub through MCP tools.
|
|
||||||
|
|
||||||
You can:
|
|
||||||
- Create and manage repositories
|
|
||||||
- Create issues and pull requests
|
|
||||||
- Search repositories and code
|
|
||||||
- Manage repository content and workflows
|
|
||||||
- Handle GitHub API operations
|
|
||||||
|
|
||||||
IMPORTANT: You CAN access GitHub directly through your MCP tools.
|
|
||||||
When asked to perform GitHub operations, USE YOUR MCP TOOLS.
|
|
||||||
|
|
||||||
Example tasks:
|
|
||||||
- "Create a new repository" → Use your repository creation tools
|
|
||||||
- "Search for issues" → Use your search tools
|
|
||||||
- "Create a pull request" → Use your PR creation tools
|
|
||||||
- "List my repositories" → Use your repository listing tools
|
|
||||||
|
|
||||||
Always use your MCP tools to interact with GitHub.""",
|
|
||||||
tools=github_tools
|
|
||||||
)
|
|
||||||
agents.append(github_agent)
|
|
||||||
logger.info("✅ GitHubAgent with MCP tools created")
|
|
||||||
else:
|
|
||||||
raise Exception("GITHUB_API_KEY not found")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"❌ Failed to create GitHubAgent with MCP tools: {str(e)}")
|
|
||||||
github_agent = LlmAgent(
|
|
||||||
name="GitHubAgent",
|
|
||||||
model="gemini-2.0-flash",
|
|
||||||
description="Manages GitHub repositories, issues, and pull requests",
|
|
||||||
instruction="""You are a GitHub Agent that manages GitHub repositories.
|
|
||||||
|
|
||||||
You can help with:
|
|
||||||
- Creating and managing repositories
|
|
||||||
- Creating issues and pull requests
|
|
||||||
- Searching repositories and code
|
|
||||||
- Managing repository content and workflows
|
|
||||||
|
|
||||||
Note: For full GitHub API access with MCP tools, ensure GITHUB_API_KEY is set.
|
|
||||||
Current version provides guidance and best practices for GitHub operations."""
|
|
||||||
)
|
|
||||||
agents.append(github_agent)
|
|
||||||
logger.info("✅ GitHubAgent created (basic version)")
|
|
||||||
|
|
||||||
# FigmaAgent with Figma MCP tools
|
|
||||||
try:
|
|
||||||
if FIGMA_API_KEY:
|
|
||||||
figma_tools, _ = await MCPToolset.from_server(
|
|
||||||
connection_params=SseServerParams(
|
|
||||||
url=COMPOSIO_FIGMA_URL,
|
|
||||||
headers={}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
figma_agent = LlmAgent(
|
|
||||||
name="FigmaAgent",
|
|
||||||
model="gemini-2.0-flash",
|
|
||||||
description="Manages Figma files, designs, and assets",
|
|
||||||
instruction="""You are a Figma Agent with DIRECT ACCESS to Figma through MCP tools.
|
|
||||||
|
|
||||||
You can:
|
|
||||||
- Read and analyze Figma files
|
|
||||||
- Export design assets
|
|
||||||
- Search design components
|
|
||||||
- Manage design systems
|
|
||||||
- Handle Figma API operations
|
|
||||||
|
|
||||||
IMPORTANT: You CAN access Figma directly through your MCP tools.
|
|
||||||
When asked to perform Figma operations, USE YOUR MCP TOOLS.
|
|
||||||
|
|
||||||
Example tasks:
|
|
||||||
- "Export design assets" → Use your export tools
|
|
||||||
- "Search for components" → Use your search tools
|
|
||||||
- "Read file information" → Use your file reading tools
|
|
||||||
- "List project files" → Use your file listing tools
|
|
||||||
|
|
||||||
Always use your MCP tools to interact with Figma.""",
|
|
||||||
tools=figma_tools
|
|
||||||
)
|
|
||||||
agents.append(figma_agent)
|
|
||||||
logger.info("✅ FigmaAgent with MCP tools created")
|
|
||||||
else:
|
|
||||||
raise Exception("FIGMA_API_KEY not found")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"❌ Failed to create FigmaAgent with MCP tools: {str(e)}")
|
|
||||||
figma_agent = LlmAgent(
|
|
||||||
name="FigmaAgent",
|
|
||||||
model="gemini-2.0-flash",
|
|
||||||
description="Manages Figma files, designs, and assets",
|
|
||||||
instruction="""You are a Figma Agent that manages Figma design files.
|
|
||||||
|
|
||||||
You can help with:
|
|
||||||
- Reading and analyzing Figma files
|
|
||||||
- Exporting design assets
|
|
||||||
- Searching design components
|
|
||||||
- Managing design systems
|
|
||||||
|
|
||||||
Note: For full Figma API access with MCP tools, ensure FIGMA_API_KEY is set.
|
|
||||||
Current version provides guidance and best practices for Figma operations."""
|
|
||||||
)
|
|
||||||
agents.append(figma_agent)
|
|
||||||
logger.info("✅ FigmaAgent created (basic version)")
|
|
||||||
|
|
||||||
return agents
|
|
||||||
|
|
||||||
class EnterpriseMCPAIAgentTeam:
|
|
||||||
"""Enterprise MCP AI Agent Team - Multi-Agent System with MCP Tools"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
"""Initialize the orchestrator"""
|
|
||||||
self.root_agent = None
|
|
||||||
self._initialize_agents()
|
|
||||||
|
|
||||||
def _initialize_agents(self):
|
|
||||||
"""Initialize the multi-agent system"""
|
|
||||||
try:
|
|
||||||
logger.info("🔧 Creating complete multi-agent system with MCP tools...")
|
|
||||||
|
|
||||||
# Create all sub-agents with MCP tools using async
|
|
||||||
loop = asyncio.new_event_loop()
|
|
||||||
asyncio.set_event_loop(loop)
|
|
||||||
sub_agents = loop.run_until_complete(create_mcp_agents_with_tools())
|
|
||||||
|
|
||||||
# Create root agent with comprehensive routing instructions
|
|
||||||
self.root_agent = LlmAgent(
|
|
||||||
name="EnterpriseMCPAIAgentTeam",
|
|
||||||
model="gemini-2.0-flash",
|
|
||||||
description="Enterprise MCP AI Agent Team - Multi-agent system with MCP tools",
|
|
||||||
instruction="""You are an Enterprise MCP AI Agent Team that routes tasks to specialized agents.
|
|
||||||
|
|
||||||
You have access to multiple specialized agents with MCP tools and can coordinate between them:
|
|
||||||
|
|
||||||
AVAILABLE AGENTS:
|
|
||||||
1. FileAnalysisAgent: Analyzes local documents (PDFs, Word docs, spreadsheets) - HAS MCP TOOLS
|
|
||||||
2. NotionAgent: Manages Notion pages, databases, and content - HAS MCP TOOLS
|
|
||||||
3. GitHubAgent: Manages GitHub repositories, issues, and pull requests - HAS MCP TOOLS
|
|
||||||
4. FigmaAgent: Manages Figma files, designs, and assets - HAS MCP TOOLS
|
|
||||||
|
|
||||||
ROUTING LOGIC:
|
|
||||||
- File/document tasks → FileAnalysisAgent
|
|
||||||
- Notion-related tasks → NotionAgent
|
|
||||||
- GitHub-related tasks → GitHubAgent
|
|
||||||
- Figma/design tasks → FigmaAgent
|
|
||||||
- Multi-platform tasks → Coordinate between relevant agents
|
|
||||||
|
|
||||||
You can:
|
|
||||||
1. Transfer tasks to specialized agents using transfer_to_agent()
|
|
||||||
2. Coordinate multi-step workflows
|
|
||||||
3. Share context between agents through session state
|
|
||||||
4. Provide comprehensive results and recommendations
|
|
||||||
|
|
||||||
EXAMPLES:
|
|
||||||
- "List files in Documents" → FileAnalysisAgent (with real file system access)
|
|
||||||
- "Search my Notion pages" → NotionAgent (with real Notion API access)
|
|
||||||
- "Create a GitHub repo" → GitHubAgent (with real GitHub API access)
|
|
||||||
- "Export Figma designs" → FigmaAgent (with real Figma API access)
|
|
||||||
|
|
||||||
IMPORTANT: Use transfer_to_agent() to delegate to the most appropriate agent for each task.
|
|
||||||
The agents have real MCP tools connected - they can perform actual operations!""",
|
|
||||||
sub_agents=sub_agents
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.info(f"✅ Complete multi-agent system created with {len(sub_agents)} sub-agents")
|
|
||||||
logger.info(f"✅ Sub-agents: {[agent.name for agent in sub_agents]}")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"❌ Failed to create complete multi-agent system: {str(e)}")
|
|
||||||
logger.info("🔄 Falling back to basic multi-agent system...")
|
|
||||||
self._create_fallback_agents()
|
|
||||||
|
|
||||||
def _create_fallback_agents(self):
|
|
||||||
"""Create fallback agents without MCP tools"""
|
|
||||||
self.root_agent = LlmAgent(
|
|
||||||
name="EnterpriseMCPAIAgentTeam",
|
|
||||||
model="gemini-2.0-flash",
|
|
||||||
description="Enterprise MCP AI Agent Team - Multi-agent system",
|
|
||||||
instruction="""You are an Enterprise MCP AI Agent Team that routes tasks to specialized agents.
|
|
||||||
|
|
||||||
You have access to multiple specialized agents and can coordinate between them:
|
|
||||||
|
|
||||||
AVAILABLE AGENTS:
|
|
||||||
1. FileAnalysisAgent: Analyzes local documents (PDFs, Word docs, spreadsheets)
|
|
||||||
2. NotionAgent: Manages Notion pages, databases, and content
|
|
||||||
3. GitHubAgent: Manages GitHub repositories, issues, and pull requests
|
|
||||||
4. FigmaAgent: Manages Figma files, designs, and assets
|
|
||||||
|
|
||||||
ROUTING LOGIC:
|
|
||||||
- File/document tasks → FileAnalysisAgent
|
|
||||||
- Notion-related tasks → NotionAgent
|
|
||||||
- GitHub-related tasks → GitHubAgent
|
|
||||||
- Figma/design tasks → FigmaAgent
|
|
||||||
- Multi-platform tasks → Coordinate between relevant agents
|
|
||||||
|
|
||||||
You can:
|
|
||||||
1. Transfer tasks to specialized agents using transfer_to_agent()
|
|
||||||
2. Coordinate multi-step workflows
|
|
||||||
3. Share context between agents through session state
|
|
||||||
4. Provide comprehensive results and recommendations
|
|
||||||
|
|
||||||
EXAMPLES:
|
|
||||||
- "List files in Documents" → FileAnalysisAgent
|
|
||||||
- "Search my Notion pages" → NotionAgent
|
|
||||||
- "Create a GitHub repo" → GitHubAgent
|
|
||||||
- "Export Figma designs" → FigmaAgent
|
|
||||||
|
|
||||||
IMPORTANT: Use transfer_to_agent() to delegate to the most appropriate agent for each task.
|
|
||||||
|
|
||||||
For full MCP tool functionality, ensure all environment variables are set correctly:
|
|
||||||
- MCP_FILESYSTEM_PATH: Path to your filesystem folder
|
|
||||||
- NOTION_API_KEY: Your Notion API key
|
|
||||||
- GITHUB_API_KEY: Your GitHub API key
|
|
||||||
- FIGMA_API_KEY: Your Figma API key""",
|
|
||||||
sub_agents=[
|
|
||||||
LlmAgent(
|
|
||||||
name="FileAnalysisAgent",
|
|
||||||
model="gemini-2.0-flash",
|
|
||||||
description="Analyzes local documents and extracts key information",
|
|
||||||
instruction="You analyze local documents (PDFs, Word docs, spreadsheets) and extract key information, summaries, and action items."
|
|
||||||
),
|
|
||||||
LlmAgent(
|
|
||||||
name="NotionAgent",
|
|
||||||
model="gemini-2.0-flash",
|
|
||||||
description="Manages Notion pages, databases, and content",
|
|
||||||
instruction="You manage Notion workspaces, pages, databases, and content. You can read, write, search, and organize Notion content."
|
|
||||||
),
|
|
||||||
LlmAgent(
|
|
||||||
name="GitHubAgent",
|
|
||||||
model="gemini-2.0-flash",
|
|
||||||
description="Manages GitHub repositories, issues, and pull requests",
|
|
||||||
instruction="You manage GitHub repositories, create issues and pull requests, search code, and handle repository operations."
|
|
||||||
),
|
|
||||||
LlmAgent(
|
|
||||||
name="FigmaAgent",
|
|
||||||
model="gemini-2.0-flash",
|
|
||||||
description="Manages Figma files, designs, and assets",
|
|
||||||
instruction="You manage Figma design files, export assets, search design components, and handle design system operations."
|
|
||||||
)
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
# Create root_agent for ADK Web compatibility
|
|
||||||
try:
|
|
||||||
orchestrator = EnterpriseMCPAIAgentTeam()
|
|
||||||
root_agent = orchestrator.root_agent
|
|
||||||
logger.info("✅ EnterpriseMCPAIAgentTeam class and root_agent created successfully")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"❌ Failed to create EnterpriseMCPAIAgentTeam: {str(e)}")
|
|
||||||
# Fallback: create basic root_agent
|
|
||||||
root_agent = LlmAgent(
|
|
||||||
name="EnterpriseMCPAIAgentTeam",
|
|
||||||
model="gemini-2.0-flash",
|
|
||||||
description="Enterprise MCP AI Agent Team - Basic multi-agent system",
|
|
||||||
instruction="You are an Enterprise MCP AI Agent Team that routes tasks to specialized agents.",
|
|
||||||
sub_agents=[]
|
|
||||||
)
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
# Google ADK and AI dependencies
|
|
||||||
google-adk>=0.1.0
|
|
||||||
google-genai>=0.3.0
|
|
||||||
|
|
||||||
# Environment and configuration
|
|
||||||
python-dotenv>=1.0.0
|
|
||||||
pydantic>=2.0.0
|
|
||||||
|
|
||||||
# Async support
|
|
||||||
asyncio-mqtt>=0.16.0
|
|
||||||
|
|
||||||
# Logging and monitoring
|
|
||||||
structlog>=23.0.0
|
|
||||||
|
|
||||||
# Data processing (optional, for advanced file analysis)
|
|
||||||
pandas>=2.0.0
|
|
||||||
numpy>=1.24.0
|
|
||||||
|
|
||||||
# File handling (optional, for document processing)
|
|
||||||
PyPDF2>=3.0.0
|
|
||||||
python-docx>=0.8.11
|
|
||||||
openpyxl>=3.1.0
|
|
||||||
|
|
||||||
# HTTP client for MCP server communication
|
|
||||||
httpx>=0.24.0
|
|
||||||
aiohttp>=3.8.0
|
|
||||||
|
|
||||||
# Development and testing
|
|
||||||
pytest>=7.0.0
|
|
||||||
pytest-asyncio>=0.21.0
|
|
||||||
black>=23.0.0
|
|
||||||
flake8>=6.0.0
|
|
||||||
Loading…
Reference in a new issue