Spaces:
Paused
Paused
Upload app.py with huggingface_hub
Browse files
app.py
CHANGED
|
@@ -5016,6 +5016,378 @@ class ProactiveIntelligence:
|
|
| 5016 |
return None
|
| 5017 |
|
| 5018 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5019 |
def call_llm_safe(messages, max_tokens=1024, temperature=0.7):
|
| 5020 |
"""BRILLIANT SMART ROUTER v2 — fully uncensored.
|
| 5021 |
|
|
@@ -6631,6 +7003,28 @@ def detect_intent(text: str, chat_id: str = "default") -> Optional[Dict[str, Any
|
|
| 6631 |
if re.search(r"\b(list|show|what)\b.*\b(available\s+)?tools?\b", text_lower):
|
| 6632 |
return {"action": "list_tools"}
|
| 6633 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6634 |
# 4a-4. Binance API key — special detection (key + secret in same message)
|
| 6635 |
if "binance" in text_lower and ("key" in text_lower or "api" in text_lower):
|
| 6636 |
# Binance API keys are 64-char alphanumeric
|
|
@@ -6844,6 +7238,29 @@ def execute_action(action: Dict[str, Any], chat_id: str = "default") -> str:
|
|
| 6844 |
f" • 'Show my BTC order history'\n\n"
|
| 6845 |
f"⚠️ I can now trade with your funds. Start with small amounts.")
|
| 6846 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6847 |
# === Background task actions ===
|
| 6848 |
if kind == "create_hf_space":
|
| 6849 |
# Extract chat_id (Telegram user ID) from chat_key like "tg_7475344894"
|
|
|
|
| 5016 |
return None
|
| 5017 |
|
| 5018 |
|
| 5019 |
+
# ============================================================================
|
| 5020 |
+
# MCP CLIENT — Model Context Protocol for unlimited external tools
|
| 5021 |
+
# ============================================================================
|
| 5022 |
+
|
| 5023 |
+
class MCPClient:
|
| 5024 |
+
"""Model Context Protocol client — connect to ANY MCP server.
|
| 5025 |
+
|
| 5026 |
+
MCP is Anthropic's open standard for AI-tool communication.
|
| 5027 |
+
This lets Hermes connect to 200+ external tools (GitHub, Slack,
|
| 5028 |
+
databases, browsers, file systems, etc.) without custom code.
|
| 5029 |
+
|
| 5030 |
+
How it works:
|
| 5031 |
+
1. User says "connect to github mcp"
|
| 5032 |
+
2. MCPClient spawns the MCP server (e.g., npx @anthropic/github-mcp)
|
| 5033 |
+
3. Does JSON-RPC handshake over stdio
|
| 5034 |
+
4. Discovers available tools
|
| 5035 |
+
5. Registers them in TOOL_REGISTRY
|
| 5036 |
+
6. Hermes can now call those tools via [[TOOL:mcp_toolname|args]]
|
| 5037 |
+
|
| 5038 |
+
Popular MCP servers:
|
| 5039 |
+
- @anthropic/github-mcp — GitHub repos, PRs, issues
|
| 5040 |
+
- @anthropic/filesystem-mcp — Read/write files
|
| 5041 |
+
- @anthropic/postgres-mcp — PostgreSQL queries
|
| 5042 |
+
- @anthropic/brave-search-mcp — Web search
|
| 5043 |
+
- @anthropic/memory-mcp — Persistent key-value memory
|
| 5044 |
+
- @anthropic/puppeteer-mcp — Browser automation
|
| 5045 |
+
- @anthropic/slack-mcp — Slack messages
|
| 5046 |
+
- @anthropic/sqlite-mcp — SQLite databases
|
| 5047 |
+
"""
|
| 5048 |
+
|
| 5049 |
+
_servers: Dict[str, Dict] = {} # name → {process, tools, config}
|
| 5050 |
+
_loaded = False
|
| 5051 |
+
|
| 5052 |
+
# Popular MCP servers for easy connection
|
| 5053 |
+
POPULAR_SERVERS = {
|
| 5054 |
+
"github": {
|
| 5055 |
+
"command": "npx",
|
| 5056 |
+
"args": ["-y", "@anthropic/github-mcp"],
|
| 5057 |
+
"env": ["GITHUB_TOKEN"],
|
| 5058 |
+
"description": "GitHub: manage repos, PRs, issues, code review",
|
| 5059 |
+
},
|
| 5060 |
+
"filesystem": {
|
| 5061 |
+
"command": "npx",
|
| 5062 |
+
"args": ["-y", "@anthropic/filesystem-mcp", "/app"],
|
| 5063 |
+
"env": [],
|
| 5064 |
+
"description": "Filesystem: read/write files on the server",
|
| 5065 |
+
},
|
| 5066 |
+
"memory": {
|
| 5067 |
+
"command": "npx",
|
| 5068 |
+
"args": ["-y", "@anthropic/memory-mcp"],
|
| 5069 |
+
"env": [],
|
| 5070 |
+
"description": "Persistent key-value memory storage",
|
| 5071 |
+
},
|
| 5072 |
+
"brave-search": {
|
| 5073 |
+
"command": "npx",
|
| 5074 |
+
"args": ["-y", "@anthropic/brave-search-mcp"],
|
| 5075 |
+
"env": ["BRAVE_API_KEY"],
|
| 5076 |
+
"description": "Advanced web search with filters",
|
| 5077 |
+
},
|
| 5078 |
+
"puppeteer": {
|
| 5079 |
+
"command": "npx",
|
| 5080 |
+
"args": ["-y", "@anthropic/puppeteer-mcp"],
|
| 5081 |
+
"env": [],
|
| 5082 |
+
"description": "Full browser automation (better than Playwright)",
|
| 5083 |
+
},
|
| 5084 |
+
"sqlite": {
|
| 5085 |
+
"command": "npx",
|
| 5086 |
+
"args": ["-y", "@anthropic/sqlite-mcp"],
|
| 5087 |
+
"env": [],
|
| 5088 |
+
"description": "SQLite database queries",
|
| 5089 |
+
},
|
| 5090 |
+
"postgres": {
|
| 5091 |
+
"command": "npx",
|
| 5092 |
+
"args": ["-y", "@anthropic/postgres-mcp"],
|
| 5093 |
+
"env": ["DATABASE_URL"],
|
| 5094 |
+
"description": "PostgreSQL database queries",
|
| 5095 |
+
},
|
| 5096 |
+
"slack": {
|
| 5097 |
+
"command": "npx",
|
| 5098 |
+
"args": ["-y", "@anthropic/slack-mcp"],
|
| 5099 |
+
"env": ["SLACK_TOKEN"],
|
| 5100 |
+
"description": "Slack: send messages, read channels",
|
| 5101 |
+
},
|
| 5102 |
+
}
|
| 5103 |
+
|
| 5104 |
+
@classmethod
|
| 5105 |
+
def _load_configs(cls):
|
| 5106 |
+
"""Load saved MCP server configs from HF memory."""
|
| 5107 |
+
if cls._loaded:
|
| 5108 |
+
return
|
| 5109 |
+
try:
|
| 5110 |
+
data = memory.read("mcp_servers.json", default={"servers": {}}) or {"servers": {}}
|
| 5111 |
+
cls._servers = data.get("servers", {})
|
| 5112 |
+
cls._loaded = True
|
| 5113 |
+
log(f"MCPClient: loaded {len(cls._servers)} server configs")
|
| 5114 |
+
except Exception:
|
| 5115 |
+
cls._servers = {}
|
| 5116 |
+
cls._loaded = True
|
| 5117 |
+
|
| 5118 |
+
@classmethod
|
| 5119 |
+
def _save_configs(cls):
|
| 5120 |
+
"""Save MCP server configs to HF memory."""
|
| 5121 |
+
try:
|
| 5122 |
+
# Only save configs (not process objects)
|
| 5123 |
+
configs = {}
|
| 5124 |
+
for name, server in cls._servers.items():
|
| 5125 |
+
configs[name] = {
|
| 5126 |
+
"command": server.get("command"),
|
| 5127 |
+
"args": server.get("args"),
|
| 5128 |
+
"env": server.get("env", {}),
|
| 5129 |
+
"tools": server.get("tools", []),
|
| 5130 |
+
"connected": server.get("process") is not None,
|
| 5131 |
+
}
|
| 5132 |
+
memory.write("mcp_servers.json", {"servers": configs})
|
| 5133 |
+
except Exception as e:
|
| 5134 |
+
log(f"MCPClient: save failed: {e}")
|
| 5135 |
+
|
| 5136 |
+
@classmethod
|
| 5137 |
+
def connect(cls, server_name: str, custom_command: str = "", custom_args: str = "") -> str:
|
| 5138 |
+
"""Connect to an MCP server.
|
| 5139 |
+
|
| 5140 |
+
Args:
|
| 5141 |
+
server_name: Name from POPULAR_SERVERS (e.g., "github") or custom name
|
| 5142 |
+
custom_command: Custom command (e.g., "npx", "node", "python")
|
| 5143 |
+
custom_args: Custom args (space-separated)
|
| 5144 |
+
|
| 5145 |
+
Returns: status message
|
| 5146 |
+
"""
|
| 5147 |
+
cls._load_configs()
|
| 5148 |
+
|
| 5149 |
+
# Check if Node.js is available
|
| 5150 |
+
try:
|
| 5151 |
+
result = subprocess.run(["node", "--version"], capture_output=True, text=True, timeout=5)
|
| 5152 |
+
if result.returncode != 0:
|
| 5153 |
+
return "❌ Node.js is not installed. MCP servers require Node.js."
|
| 5154 |
+
except Exception:
|
| 5155 |
+
return "❌ Node.js is not installed. MCP servers require Node.js."
|
| 5156 |
+
|
| 5157 |
+
# Determine command and args
|
| 5158 |
+
if server_name in cls.POPULAR_SERVERS and not custom_command:
|
| 5159 |
+
config = cls.POPULAR_SERVERS[server_name]
|
| 5160 |
+
command = config["command"]
|
| 5161 |
+
args = config["args"]
|
| 5162 |
+
elif custom_command:
|
| 5163 |
+
command = custom_command
|
| 5164 |
+
args = custom_args.split() if custom_args else []
|
| 5165 |
+
else:
|
| 5166 |
+
available = ", ".join(cls.POPULAR_SERVERS.keys())
|
| 5167 |
+
return f"❌ Unknown server '{server_name}'. Available: {available}\nOr use: connect to custom mcp: COMMAND ARGS"
|
| 5168 |
+
|
| 5169 |
+
log(f"MCPClient: connecting to {server_name} ({command} {' '.join(args)})...")
|
| 5170 |
+
|
| 5171 |
+
# Prepare environment
|
| 5172 |
+
env = os.environ.copy()
|
| 5173 |
+
# Add any required env vars from vault
|
| 5174 |
+
server_config = cls.POPULAR_SERVERS.get(server_name, {})
|
| 5175 |
+
for env_var in server_config.get("env", []):
|
| 5176 |
+
env_key = env_var.lower()
|
| 5177 |
+
if vault.has(env_key):
|
| 5178 |
+
env[env_var] = vault.get(env_key)
|
| 5179 |
+
|
| 5180 |
+
try:
|
| 5181 |
+
# Spawn the MCP server process
|
| 5182 |
+
process = subprocess.Popen(
|
| 5183 |
+
[command] + args,
|
| 5184 |
+
stdin=subprocess.PIPE,
|
| 5185 |
+
stdout=subprocess.PIPE,
|
| 5186 |
+
stderr=subprocess.PIPE,
|
| 5187 |
+
env=env,
|
| 5188 |
+
text=True,
|
| 5189 |
+
bufsize=1,
|
| 5190 |
+
)
|
| 5191 |
+
|
| 5192 |
+
# JSON-RPC handshake
|
| 5193 |
+
# Step 1: Send initialize request
|
| 5194 |
+
init_request = {
|
| 5195 |
+
"jsonrpc": "2.0",
|
| 5196 |
+
"id": 1,
|
| 5197 |
+
"method": "initialize",
|
| 5198 |
+
"params": {
|
| 5199 |
+
"protocolVersion": "2024-11-05",
|
| 5200 |
+
"capabilities": {},
|
| 5201 |
+
"clientInfo": {"name": "hermes-agent", "version": "1.0.0"}
|
| 5202 |
+
}
|
| 5203 |
+
}
|
| 5204 |
+
process.stdin.write(json.dumps(init_request) + "\n")
|
| 5205 |
+
process.stdin.flush()
|
| 5206 |
+
|
| 5207 |
+
# Read response (with timeout)
|
| 5208 |
+
import select
|
| 5209 |
+
readable, _, _ = select.select([process.stdout], [], [], 10)
|
| 5210 |
+
if not readable:
|
| 5211 |
+
process.kill()
|
| 5212 |
+
return f"❌ MCP server '{server_name}' didn't respond (timeout). Check if the package exists."
|
| 5213 |
+
|
| 5214 |
+
response_line = process.stdout.readline()
|
| 5215 |
+
if not response_line:
|
| 5216 |
+
process.kill()
|
| 5217 |
+
return f"❌ MCP server '{server_name}' closed connection."
|
| 5218 |
+
|
| 5219 |
+
init_response = json.loads(response_line)
|
| 5220 |
+
if "error" in init_response:
|
| 5221 |
+
process.kill()
|
| 5222 |
+
return f"❌ MCP server '{server_name}' error: {init_response['error']}"
|
| 5223 |
+
|
| 5224 |
+
# Step 2: Send initialized notification
|
| 5225 |
+
initialized_notif = {
|
| 5226 |
+
"jsonrpc": "2.0",
|
| 5227 |
+
"method": "notifications/initialized",
|
| 5228 |
+
}
|
| 5229 |
+
process.stdin.write(json.dumps(initialized_notif) + "\n")
|
| 5230 |
+
process.stdin.flush()
|
| 5231 |
+
|
| 5232 |
+
# Step 3: List available tools
|
| 5233 |
+
list_tools_request = {
|
| 5234 |
+
"jsonrpc": "2.0",
|
| 5235 |
+
"id": 2,
|
| 5236 |
+
"method": "tools/list",
|
| 5237 |
+
"params": {}
|
| 5238 |
+
}
|
| 5239 |
+
process.stdin.write(json.dumps(list_tools_request) + "\n")
|
| 5240 |
+
process.stdin.flush()
|
| 5241 |
+
|
| 5242 |
+
readable, _, _ = select.select([process.stdout], [], [], 5)
|
| 5243 |
+
if not readable:
|
| 5244 |
+
process.kill()
|
| 5245 |
+
return f"❌ MCP server '{server_name}' didn't return tools list."
|
| 5246 |
+
|
| 5247 |
+
tools_response_line = process.stdout.readline()
|
| 5248 |
+
tools_response = json.loads(tools_response_line)
|
| 5249 |
+
tools = tools_response.get("result", {}).get("tools", [])
|
| 5250 |
+
|
| 5251 |
+
# Store the server connection
|
| 5252 |
+
cls._servers[server_name] = {
|
| 5253 |
+
"command": command,
|
| 5254 |
+
"args": args,
|
| 5255 |
+
"process": process,
|
| 5256 |
+
"tools": tools,
|
| 5257 |
+
"connected": True,
|
| 5258 |
+
}
|
| 5259 |
+
|
| 5260 |
+
# Register tools in TOOL_REGISTRY
|
| 5261 |
+
registered = 0
|
| 5262 |
+
for tool in tools:
|
| 5263 |
+
tool_name = f"mcp_{server_name}_{tool['name']}"
|
| 5264 |
+
tool_desc = tool.get("description", "")[:100]
|
| 5265 |
+
# Create a closure to call this tool
|
| 5266 |
+
def make_caller(srv, tn):
|
| 5267 |
+
def caller(**kwargs):
|
| 5268 |
+
return cls.call_tool(srv, tn, kwargs)
|
| 5269 |
+
return caller
|
| 5270 |
+
TOOL_REGISTRY[tool_name] = make_caller(server_name, tool["name"])
|
| 5271 |
+
registered += 1
|
| 5272 |
+
|
| 5273 |
+
cls._save_configs()
|
| 5274 |
+
log(f"MCPClient: connected to {server_name}, registered {registered} tools")
|
| 5275 |
+
|
| 5276 |
+
tool_list = "\n".join(f" • mcp_{server_name}_{t['name']}: {t.get('description','')[:60]}" for t in tools[:10])
|
| 5277 |
+
return (f"✅ Connected to {server_name} MCP server!\n\n"
|
| 5278 |
+
f"Registered {registered} tools:\n{tool_list}"
|
| 5279 |
+
f"\n\nYou can now use these tools. Example: 'Use mcp_{server_name}_{tools[0]['name'] if tools else 'tool'}'")
|
| 5280 |
+
|
| 5281 |
+
except json.JSONDecodeError as e:
|
| 5282 |
+
return f"❌ MCP handshake failed (invalid JSON): {e}"
|
| 5283 |
+
except Exception as e:
|
| 5284 |
+
return f"❌ MCP connection failed: {e}"
|
| 5285 |
+
|
| 5286 |
+
@classmethod
|
| 5287 |
+
def call_tool(cls, server_name: str, tool_name: str, args: dict) -> str:
|
| 5288 |
+
"""Call a tool on an MCP server."""
|
| 5289 |
+
cls._load_configs()
|
| 5290 |
+
server = cls._servers.get(server_name)
|
| 5291 |
+
if not server or not server.get("process"):
|
| 5292 |
+
return f"MCP server '{server_name}' is not connected. Say 'connect to {server_name} mcp' first."
|
| 5293 |
+
|
| 5294 |
+
process = server["process"]
|
| 5295 |
+
if process.poll() is not None:
|
| 5296 |
+
# Process died — try to reconnect
|
| 5297 |
+
log(f"MCPClient: {server_name} process died, reconnecting...")
|
| 5298 |
+
cls._servers[server_name]["process"] = None
|
| 5299 |
+
reconnect_result = cls.connect(server_name)
|
| 5300 |
+
if "✅" not in reconnect_result:
|
| 5301 |
+
return f"MCP server '{server_name}' disconnected and couldn't reconnect."
|
| 5302 |
+
server = cls._servers.get(server_name)
|
| 5303 |
+
process = server["process"]
|
| 5304 |
+
|
| 5305 |
+
try:
|
| 5306 |
+
request = {
|
| 5307 |
+
"jsonrpc": "2.0",
|
| 5308 |
+
"id": int(time.time()),
|
| 5309 |
+
"method": "tools/call",
|
| 5310 |
+
"params": {
|
| 5311 |
+
"name": tool_name,
|
| 5312 |
+
"arguments": args,
|
| 5313 |
+
}
|
| 5314 |
+
}
|
| 5315 |
+
process.stdin.write(json.dumps(request) + "\n")
|
| 5316 |
+
process.stdin.flush()
|
| 5317 |
+
|
| 5318 |
+
import select
|
| 5319 |
+
readable, _, _ = select.select([process.stdout], [], [], 30)
|
| 5320 |
+
if not readable:
|
| 5321 |
+
return f"MCP tool '{tool_name}' timed out (30s)."
|
| 5322 |
+
|
| 5323 |
+
response_line = process.stdout.readline()
|
| 5324 |
+
response = json.loads(response_line)
|
| 5325 |
+
|
| 5326 |
+
if "error" in response:
|
| 5327 |
+
return f"MCP error: {response['error']}"
|
| 5328 |
+
|
| 5329 |
+
result = response.get("result", {})
|
| 5330 |
+
# MCP returns content as array of {type, text}
|
| 5331 |
+
content = result.get("content", [])
|
| 5332 |
+
if content:
|
| 5333 |
+
texts = [c.get("text", "") for c in content if c.get("type") == "text"]
|
| 5334 |
+
return "\n".join(texts) if texts else str(result)
|
| 5335 |
+
return str(result)
|
| 5336 |
+
|
| 5337 |
+
except Exception as e:
|
| 5338 |
+
return f"MCP tool call failed: {e}"
|
| 5339 |
+
|
| 5340 |
+
@classmethod
|
| 5341 |
+
def disconnect(cls, server_name: str) -> str:
|
| 5342 |
+
"""Disconnect from an MCP server."""
|
| 5343 |
+
cls._load_configs()
|
| 5344 |
+
server = cls._servers.get(server_name)
|
| 5345 |
+
if not server:
|
| 5346 |
+
return f"MCP server '{server_name}' not found."
|
| 5347 |
+
|
| 5348 |
+
process = server.get("process")
|
| 5349 |
+
if process:
|
| 5350 |
+
try:
|
| 5351 |
+
process.kill()
|
| 5352 |
+
except Exception:
|
| 5353 |
+
pass
|
| 5354 |
+
|
| 5355 |
+
# Remove tools from TOOL_REGISTRY
|
| 5356 |
+
tools_to_remove = [k for k in TOOL_REGISTRY.keys() if k.startswith(f"mcp_{server_name}_")]
|
| 5357 |
+
for t in tools_to_remove:
|
| 5358 |
+
del TOOL_REGISTRY[t]
|
| 5359 |
+
|
| 5360 |
+
del cls._servers[server_name]
|
| 5361 |
+
cls._save_configs()
|
| 5362 |
+
return f"✅ Disconnected from {server_name} MCP server (removed {len(tools_to_remove)} tools)."
|
| 5363 |
+
|
| 5364 |
+
@classmethod
|
| 5365 |
+
def list_servers(cls) -> str:
|
| 5366 |
+
"""List all available and connected MCP servers."""
|
| 5367 |
+
cls._load_configs()
|
| 5368 |
+
lines = ["🔌 MCP Servers\n"]
|
| 5369 |
+
|
| 5370 |
+
# Show popular servers
|
| 5371 |
+
lines.append("Available servers (say 'connect to X mcp'):")
|
| 5372 |
+
for name, config in cls.POPULAR_SERVERS.items():
|
| 5373 |
+
connected = "✅" if name in cls._servers and cls._servers[name].get("process") else "⚪"
|
| 5374 |
+
env_req = f" (needs {', '.join(config['env'])})" if config.get("env") else ""
|
| 5375 |
+
lines.append(f" {connected} {name}: {config['description']}{env_req}")
|
| 5376 |
+
|
| 5377 |
+
# Show custom connected servers
|
| 5378 |
+
custom = [n for n in cls._servers.keys() if n not in cls.POPULAR_SERVERS]
|
| 5379 |
+
if custom:
|
| 5380 |
+
lines.append(f"\nCustom servers:")
|
| 5381 |
+
for name in custom:
|
| 5382 |
+
lines.append(f" ✅ {name}")
|
| 5383 |
+
|
| 5384 |
+
# Show total tools
|
| 5385 |
+
total_tools = sum(len(s.get("tools", [])) for s in cls._servers.values() if s.get("process"))
|
| 5386 |
+
lines.append(f"\nTotal MCP tools available: {total_tools}")
|
| 5387 |
+
|
| 5388 |
+
return "\n".join(lines)
|
| 5389 |
+
|
| 5390 |
+
|
| 5391 |
def call_llm_safe(messages, max_tokens=1024, temperature=0.7):
|
| 5392 |
"""BRILLIANT SMART ROUTER v2 — fully uncensored.
|
| 5393 |
|
|
|
|
| 7003 |
if re.search(r"\b(list|show|what)\b.*\b(available\s+)?tools?\b", text_lower):
|
| 7004 |
return {"action": "list_tools"}
|
| 7005 |
|
| 7006 |
+
# 4a-5. MCP commands — connect, disconnect, list MCP servers
|
| 7007 |
+
if "mcp" in text_lower:
|
| 7008 |
+
# Connect: "connect to github mcp" / "connect to filesystem mcp"
|
| 7009 |
+
if re.search(r"\b(connect|start|enable|add)\b.*\b(to\s+)?(\w+)\s+mcp\b", text_lower):
|
| 7010 |
+
match = re.search(r"\b(to\s+)?(\w+)\s+mcp\b", text_lower)
|
| 7011 |
+
server_name = match.group(2) if match else ""
|
| 7012 |
+
return {"action": "mcp_connect", "server_name": server_name}
|
| 7013 |
+
# Custom MCP: "connect to custom mcp: npx some-package"
|
| 7014 |
+
if "custom mcp" in text_lower and ":" in text:
|
| 7015 |
+
parts = text.split(":", 1)
|
| 7016 |
+
if len(parts) >= 2:
|
| 7017 |
+
cmd_parts = parts[1].strip().split(None, 1)
|
| 7018 |
+
return {"action": "mcp_connect_custom", "command": cmd_parts[0] if cmd_parts else "", "args": cmd_parts[1] if len(cmd_parts) > 1 else ""}
|
| 7019 |
+
# Disconnect: "disconnect github mcp" / "remove github mcp"
|
| 7020 |
+
if re.search(r"\b(disconnect|remove|stop|disable)\b.*\b(\w+)\s+mcp\b", text_lower):
|
| 7021 |
+
match = re.search(r"\b(\w+)\s+mcp\b", text_lower)
|
| 7022 |
+
server_name = match.group(1) if match else ""
|
| 7023 |
+
return {"action": "mcp_disconnect", "server_name": server_name}
|
| 7024 |
+
# List: "list mcp servers" / "show mcp"
|
| 7025 |
+
if re.search(r"\b(list|show|available)\b.*mcp", text_lower):
|
| 7026 |
+
return {"action": "mcp_list"}
|
| 7027 |
+
|
| 7028 |
# 4a-4. Binance API key — special detection (key + secret in same message)
|
| 7029 |
if "binance" in text_lower and ("key" in text_lower or "api" in text_lower):
|
| 7030 |
# Binance API keys are 64-char alphanumeric
|
|
|
|
| 7238 |
f" • 'Show my BTC order history'\n\n"
|
| 7239 |
f"⚠️ I can now trade with your funds. Start with small amounts.")
|
| 7240 |
|
| 7241 |
+
# === MCP actions ===
|
| 7242 |
+
if kind == "mcp_connect":
|
| 7243 |
+
server_name = action.get("server_name", "").lower()
|
| 7244 |
+
if not server_name:
|
| 7245 |
+
return "Which MCP server? Say 'list mcp' to see available servers, or 'connect to github mcp'."
|
| 7246 |
+
return MCPClient.connect(server_name)
|
| 7247 |
+
|
| 7248 |
+
if kind == "mcp_connect_custom":
|
| 7249 |
+
command = action.get("command", "")
|
| 7250 |
+
args = action.get("args", "")
|
| 7251 |
+
if not command:
|
| 7252 |
+
return "Please specify a command. Example: 'connect to custom mcp: npx @some/mcp-server'"
|
| 7253 |
+
return MCPClient.connect("custom", custom_command=command, custom_args=args)
|
| 7254 |
+
|
| 7255 |
+
if kind == "mcp_disconnect":
|
| 7256 |
+
server_name = action.get("server_name", "").lower()
|
| 7257 |
+
if not server_name:
|
| 7258 |
+
return "Which MCP server? Say 'disconnect github mcp'."
|
| 7259 |
+
return MCPClient.disconnect(server_name)
|
| 7260 |
+
|
| 7261 |
+
if kind == "mcp_list":
|
| 7262 |
+
return MCPClient.list_servers()
|
| 7263 |
+
|
| 7264 |
# === Background task actions ===
|
| 7265 |
if kind == "create_hf_space":
|
| 7266 |
# Extract chat_id (Telegram user ID) from chat_key like "tg_7475344894"
|