mindroot 9.3.0__py3-none-any.whl → 9.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mindroot/coreplugins/admin/__init__.py +3 -1
- mindroot/coreplugins/admin/agent_router.py +250 -7
- mindroot/coreplugins/admin/asset_manager.py +164 -0
- mindroot/coreplugins/admin/command_router.py +236 -1
- mindroot/coreplugins/admin/mcp_catalog_routes.py +156 -0
- mindroot/coreplugins/admin/mcp_publish_routes.py +450 -0
- mindroot/coreplugins/admin/mcp_registry_routes.py +495 -0
- mindroot/coreplugins/admin/mcp_routes.py +216 -0
- mindroot/coreplugins/admin/mod.py +62 -0
- mindroot/coreplugins/admin/oauth_callback_router.py +84 -0
- mindroot/coreplugins/admin/persona_handler.py +15 -6
- mindroot/coreplugins/admin/persona_router.py +158 -2
- mindroot/coreplugins/admin/plugin_manager.py +105 -9
- mindroot/coreplugins/admin/plugin_router_fixed.py +23 -0
- mindroot/coreplugins/admin/plugin_router_new_not_working.py +145 -0
- mindroot/coreplugins/admin/plugin_routes.py +114 -0
- mindroot/coreplugins/admin/registry_settings_routes.py +140 -0
- mindroot/coreplugins/admin/router.py +116 -15
- mindroot/coreplugins/admin/service_models.py +1 -1
- mindroot/coreplugins/admin/settings_router.py +1 -0
- mindroot/coreplugins/admin/static/css/admin-custom.css +357 -2
- mindroot/coreplugins/admin/static/css/dark.css +1 -0
- mindroot/coreplugins/admin/static/css/default.css +4 -0
- mindroot/coreplugins/admin/static/js/about-info.js +367 -0
- mindroot/coreplugins/admin/static/js/agent-form.js +83 -3
- mindroot/coreplugins/admin/static/js/api-key-script.js +307 -0
- mindroot/coreplugins/admin/static/js/mcp-manager.js +348 -0
- mindroot/coreplugins/admin/static/js/mcp-publisher.js +780 -0
- mindroot/coreplugins/admin/static/js/persona-editor.js +34 -5
- mindroot/coreplugins/admin/static/js/plugin-toggle.js +1 -1
- mindroot/coreplugins/admin/static/js/recommended-plugin-install.js +63 -0
- mindroot/coreplugins/admin/static/js/registry-auth-section.js +132 -0
- mindroot/coreplugins/admin/static/js/registry-manager-base.js +613 -0
- mindroot/coreplugins/admin/static/js/registry-manager-publish-old-delete.js +166 -0
- mindroot/coreplugins/admin/static/js/registry-manager.js +351 -0
- mindroot/coreplugins/admin/static/js/registry-publish-section.js +377 -0
- mindroot/coreplugins/admin/static/js/registry-search-section.js +400 -0
- mindroot/coreplugins/admin/static/js/registry-search-section.js.bak +3 -0
- mindroot/coreplugins/admin/static/js/registry-settings.js +69 -0
- mindroot/coreplugins/admin/static/js/registry-shared-services.js +903 -0
- mindroot/coreplugins/admin/static/js/registry-simple-sections.js +85 -0
- mindroot/coreplugins/admin/static/js/secure-widget-manager.js +438 -0
- mindroot/coreplugins/admin/static/logo.png +0 -0
- mindroot/coreplugins/admin/templates/admin.jinja2 +275 -110
- mindroot/coreplugins/agent/Assistant/agent.json +27 -11
- mindroot/coreplugins/agent/agent.py +2 -2
- mindroot/coreplugins/agent/command_parser.py +25 -10
- mindroot/coreplugins/agent/templates/system.jinja2 +0 -12
- mindroot/coreplugins/chat/__init__.py +4 -1
- mindroot/coreplugins/chat/router.py +132 -20
- mindroot/coreplugins/chat/router_dedup_patch.py +20 -0
- mindroot/coreplugins/chat/services.py +31 -1
- mindroot/coreplugins/chat/static/css/action-fix.css +32 -0
- mindroot/coreplugins/chat/static/css/admin-custom.css +5 -3
- mindroot/coreplugins/chat/static/css/dark.css +24 -3
- mindroot/coreplugins/chat/static/css/default.css +24 -3
- mindroot/coreplugins/chat/static/css/main.css +1 -0
- mindroot/coreplugins/chat/static/js/action.js +137 -60
- mindroot/coreplugins/chat/static/js/chat-history.js +3 -0
- mindroot/coreplugins/chat/static/js/chat.js +59 -16
- mindroot/coreplugins/chat/static/js/chat.js.diff +221 -0
- mindroot/coreplugins/chat/static/js/chatform.js +2 -2
- mindroot/coreplugins/chat/static/site.webmanifest +1 -1
- mindroot/coreplugins/chat/templates/chat.jinja2 +3 -3
- mindroot/coreplugins/chat/widget_manager.py +139 -0
- mindroot/coreplugins/chat/widget_routes.py +287 -0
- mindroot/coreplugins/check_list/inject/admin.jinja2 +1 -1
- mindroot/coreplugins/email/__init__.py +2 -0
- mindroot/coreplugins/email/email_provider.py +2 -2
- mindroot/coreplugins/email/mod.py +100 -0
- mindroot/coreplugins/email/services.py +5 -3
- mindroot/coreplugins/email/smtp_handler.py +9 -3
- mindroot/coreplugins/email/test_email_service.py +75 -0
- mindroot/coreplugins/env_manager/mod.py +61 -25
- mindroot/coreplugins/home/router.py +37 -2
- mindroot/coreplugins/home/static/imgs/logo.png +0 -0
- mindroot/coreplugins/home/static/imgs/logo.png.bak +0 -0
- mindroot/coreplugins/home/static/imgs/logo_teal.png +0 -0
- mindroot/coreplugins/home/static/imgs/logo_teal2.png +0 -0
- mindroot/coreplugins/home/static/imgs/logo_teal_detailed.png +0 -0
- mindroot/coreplugins/home/static/imgs/logo_teal_python.png +0 -0
- mindroot/coreplugins/home/templates/home.jinja2 +15 -6
- mindroot/coreplugins/index/indices/default/index.json +39 -6
- mindroot/coreplugins/jwt_auth/middleware.py +47 -2
- mindroot/coreplugins/jwt_auth/mod.py +40 -17
- mindroot/coreplugins/l8n/__init__.py +6 -0
- mindroot/coreplugins/l8n/debug_loader.py +85 -0
- mindroot/coreplugins/l8n/debug_middleware.py +74 -0
- mindroot/coreplugins/l8n/l8n_constants.py +19 -0
- mindroot/coreplugins/l8n/language_detection.py +183 -0
- mindroot/coreplugins/l8n/middleware.py +151 -0
- mindroot/coreplugins/l8n/mod.py +277 -0
- mindroot/coreplugins/l8n/monkey_patch_to_delete.py +186 -0
- mindroot/coreplugins/l8n/test_enhanced.py +298 -0
- mindroot/coreplugins/l8n/test_l8n.py +95 -0
- mindroot/coreplugins/l8n/test_l8n_standalone.py +251 -0
- mindroot/coreplugins/l8n/test_middleware.py +272 -0
- mindroot/coreplugins/l8n/utils.py +232 -0
- mindroot/coreplugins/mcp_/__init__.py +14 -0
- mindroot/coreplugins/mcp_/catalog_commands.py +328 -0
- mindroot/coreplugins/mcp_/catalog_manager.py +263 -0
- mindroot/coreplugins/mcp_/dynamic_commands.py +154 -0
- mindroot/coreplugins/mcp_/mcp_manager.py +1031 -0
- mindroot/coreplugins/mcp_/mod.py +367 -0
- mindroot/coreplugins/mcp_/oauth_storage.py +144 -0
- mindroot/coreplugins/mcp_/server_installer.py +79 -0
- mindroot/coreplugins/mcp_/setup.py +26 -0
- mindroot/coreplugins/mcp_/test_dynamic_commands.py +134 -0
- mindroot/coreplugins/mcp_/testmcpclient.py +92 -0
- mindroot/coreplugins/persona/mod.py +12 -7
- mindroot/coreplugins/signup/templates/signup.jinja2 +1 -1
- mindroot/coreplugins/subscriptions/__init__.py +1 -0
- mindroot/coreplugins/subscriptions/mod.py +14 -3
- mindroot/coreplugins/subscriptions/router.py +3 -0
- mindroot/coreplugins/user_service/__init__.py +1 -2
- mindroot/coreplugins/user_service/admin_init.py +1 -0
- mindroot/coreplugins/user_service/email_service.py +72 -17
- mindroot/coreplugins/user_service/mod.py +10 -2
- mindroot/coreplugins/user_service/router.py +2 -0
- mindroot/lib/auth/api_key.py +28 -0
- mindroot/lib/cli/plugins.py +94 -0
- mindroot/lib/plugins/default_plugin_manifest.json +20 -0
- mindroot/lib/plugins/installation.py +5 -5
- mindroot/lib/plugins/l8n_static_handler.py +225 -0
- mindroot/lib/plugins/loader.py +33 -3
- mindroot/lib/plugins/loader_with_l8n.py +281 -0
- mindroot/lib/plugins/manifest.py +236 -24
- mindroot/lib/providers/commands.py +3 -1
- mindroot/lib/route_decorators.py +5 -5
- mindroot/lib/templates.py +183 -11
- mindroot/lib/utils/merge_arrays.py +1 -1
- mindroot/migrate.py +39 -20
- mindroot/registry/data_access.py +1 -1
- mindroot/server.py +42 -13
- mindroot/server_missing_normal_args.py +197 -0
- mindroot/server_prev.py +173 -0
- {mindroot-9.3.0.dist-info → mindroot-9.6.0.dist-info}/METADATA +7 -2
- {mindroot-9.3.0.dist-info → mindroot-9.6.0.dist-info}/RECORD +143 -113
- mindroot/coreplugins/admin/plugin_manager_backup.py +0 -615
- mindroot/coreplugins/admin/static/favicon/about.txt +0 -6
- mindroot/coreplugins/admin/static/favicon/android-chrome-512x512.png +0 -0
- mindroot/coreplugins/admin/static/favicon/apple-touch-icon.png +0 -0
- mindroot/coreplugins/admin/static/favicon/favicon-16x16.png +0 -0
- mindroot/coreplugins/admin/static/favicon/favicon-32x32.png +0 -0
- mindroot/coreplugins/admin/static/favicon/favicon.ico +0 -0
- mindroot/coreplugins/admin/static/favicon/favicon_io (1)/about.txt +0 -6
- mindroot/coreplugins/admin/static/favicon/favicon_io (1)/android-chrome-192x192.png +0 -0
- mindroot/coreplugins/admin/static/favicon/favicon_io (1)/android-chrome-512x512.png +0 -0
- mindroot/coreplugins/admin/static/favicon/favicon_io (1)/apple-touch-icon.png +0 -0
- mindroot/coreplugins/admin/static/favicon/favicon_io (1)/favicon-16x16.png +0 -0
- mindroot/coreplugins/admin/static/favicon/favicon_io (1)/favicon-32x32.png +0 -0
- mindroot/coreplugins/admin/static/favicon/favicon_io (1)/favicon.ico +0 -0
- mindroot/coreplugins/admin/static/favicon/favicon_io (1)/site.webmanifest +0 -1
- mindroot/coreplugins/admin/static/favicon/logo.png +0 -0
- mindroot/coreplugins/admin/static/favicon/site.webmanifest +0 -1
- mindroot/coreplugins/admin/static/js/backup/agent-editor.js +0 -186
- mindroot/coreplugins/admin/static/js/backup/agent-form.js +0 -1133
- mindroot/coreplugins/admin/static/js/backup/agent-list.js +0 -94
- mindroot/coreplugins/chat/static/favicon/about.txt +0 -6
- mindroot/coreplugins/chat/static/favicon/android-chrome-192x192.png +0 -0
- mindroot/coreplugins/chat/static/favicon/android-chrome-512x512.png +0 -0
- mindroot/coreplugins/chat/static/favicon/apple-touch-icon.png +0 -0
- mindroot/coreplugins/chat/static/favicon/favicon-16x16.png +0 -0
- mindroot/coreplugins/chat/static/favicon/favicon-32x32.png +0 -0
- mindroot/coreplugins/chat/static/favicon/favicon.ico +0 -0
- mindroot/coreplugins/chat/static/favicon/favicon_io (1)/about.txt +0 -6
- mindroot/coreplugins/chat/static/favicon/favicon_io (1)/android-chrome-192x192.png +0 -0
- mindroot/coreplugins/chat/static/favicon/favicon_io (1)/android-chrome-512x512.png +0 -0
- mindroot/coreplugins/chat/static/favicon/favicon_io (1)/apple-touch-icon.png +0 -0
- mindroot/coreplugins/chat/static/favicon/favicon_io (1)/favicon-16x16.png +0 -0
- mindroot/coreplugins/chat/static/favicon/favicon_io (1)/favicon-32x32.png +0 -0
- mindroot/coreplugins/chat/static/favicon/favicon_io (1)/favicon.ico +0 -0
- mindroot/coreplugins/chat/static/favicon/favicon_io (1)/site.webmanifest +0 -1
- mindroot/coreplugins/chat/static/favicon/logo.png +0 -0
- mindroot/coreplugins/chat/static/favicon/site.webmanifest +0 -1
- mindroot/coreplugins/index/default.json +0 -76
- mindroot/coreplugins/user_service/file_trigger_service.py +0 -12
- mindroot/coreplugins/user_service/hooks.py +0 -23
- /mindroot/coreplugins/{admin/static/favicon/android-chrome-192x192.png → home/static/imgs/backuplogo.png} +0 -0
- {mindroot-9.3.0.dist-info → mindroot-9.6.0.dist-info}/WHEEL +0 -0
- {mindroot-9.3.0.dist-info → mindroot-9.6.0.dist-info}/entry_points.txt +0 -0
- {mindroot-9.3.0.dist-info → mindroot-9.6.0.dist-info}/licenses/LICENSE +0 -0
- {mindroot-9.3.0.dist-info → mindroot-9.6.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import asyncio
|
|
3
|
+
from typing import Dict, List, Optional, Any
|
|
4
|
+
from lib.providers.commands import command
|
|
5
|
+
from .catalog_manager import MCPCatalogManager
|
|
6
|
+
from .mod import mcp_manager, MCPServer
|
|
7
|
+
|
|
8
|
+
# Global catalog manager
|
|
9
|
+
# Use current working directory for data files
|
|
10
|
+
catalog_manager = MCPCatalogManager(working_dir=os.getcwd())
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@command()
|
|
14
|
+
async def mcp_catalog_list(category: str = None, context=None):
|
|
15
|
+
"""List MCP servers from catalog
|
|
16
|
+
|
|
17
|
+
Example:
|
|
18
|
+
{ "mcp_catalog_list": {} }
|
|
19
|
+
{ "mcp_catalog_list": { "category": "utilities" } }
|
|
20
|
+
"""
|
|
21
|
+
# Update running status first
|
|
22
|
+
catalog = catalog_manager.update_server_status()
|
|
23
|
+
|
|
24
|
+
if category:
|
|
25
|
+
servers = catalog_manager.get_servers_by_category(category)
|
|
26
|
+
else:
|
|
27
|
+
servers = catalog.get("servers", {})
|
|
28
|
+
|
|
29
|
+
# Format for display
|
|
30
|
+
result = []
|
|
31
|
+
for name, server in servers.items():
|
|
32
|
+
result.append({
|
|
33
|
+
"name": name,
|
|
34
|
+
"display_name": server.get("display_name", name),
|
|
35
|
+
"description": server.get("description", ""),
|
|
36
|
+
"category": server.get("category", "unknown"),
|
|
37
|
+
"install_method": server.get("install_method", "manual"),
|
|
38
|
+
"installed": server.get("installed", False),
|
|
39
|
+
"running": server.get("running", False),
|
|
40
|
+
"status": server.get("status", "unknown"),
|
|
41
|
+
"tools": server.get("tools", []),
|
|
42
|
+
"tags": server.get("tags", [])
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
"servers": result,
|
|
47
|
+
"count": len(result),
|
|
48
|
+
"categories": catalog_manager.get_categories()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@command()
|
|
53
|
+
async def mcp_catalog_search(query: str, context=None):
|
|
54
|
+
"""Search MCP servers in catalog
|
|
55
|
+
|
|
56
|
+
Example:
|
|
57
|
+
{ "mcp_catalog_search": { "query": "calculator" } }
|
|
58
|
+
{ "mcp_catalog_search": { "query": "database" } }
|
|
59
|
+
"""
|
|
60
|
+
servers = catalog_manager.search_servers(query)
|
|
61
|
+
|
|
62
|
+
result = []
|
|
63
|
+
for name, server in servers.items():
|
|
64
|
+
result.append({
|
|
65
|
+
"name": name,
|
|
66
|
+
"display_name": server.get("display_name", name),
|
|
67
|
+
"description": server.get("description", ""),
|
|
68
|
+
"category": server.get("category", "unknown"),
|
|
69
|
+
"installed": server.get("installed", False),
|
|
70
|
+
"running": server.get("running", False),
|
|
71
|
+
"tags": server.get("tags", [])
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
"query": query,
|
|
76
|
+
"results": result,
|
|
77
|
+
"count": len(result)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@command()
|
|
82
|
+
async def mcp_catalog_install(server_name: str, context=None):
|
|
83
|
+
"""Install an MCP server from catalog
|
|
84
|
+
|
|
85
|
+
Example:
|
|
86
|
+
{ "mcp_catalog_install": { "server_name": "calculator" } }
|
|
87
|
+
"""
|
|
88
|
+
server_info = catalog_manager.get_server_info(server_name)
|
|
89
|
+
if not server_info:
|
|
90
|
+
return f"Server {server_name} not found in catalog"
|
|
91
|
+
|
|
92
|
+
# Create server config from catalog info
|
|
93
|
+
server = MCPServer(
|
|
94
|
+
name=server_info["name"],
|
|
95
|
+
description=server_info["description"],
|
|
96
|
+
command=server_info["command"],
|
|
97
|
+
args=server_info["args"],
|
|
98
|
+
env=server_info.get("env", {})
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# Add to manager
|
|
102
|
+
mcp_manager.add_server(server_name, server)
|
|
103
|
+
|
|
104
|
+
# Install server if needed (for uvx/npx packages)
|
|
105
|
+
install_method = server_info.get("install_method", "manual")
|
|
106
|
+
install_package = server_info.get("install_package")
|
|
107
|
+
|
|
108
|
+
if install_method in ["uvx", "npx"] and install_package:
|
|
109
|
+
try:
|
|
110
|
+
import subprocess
|
|
111
|
+
if install_method == "uvx":
|
|
112
|
+
result = subprocess.run(["uvx", "--help"], capture_output=True)
|
|
113
|
+
if result.returncode != 0:
|
|
114
|
+
return f"uvx not available. Please install uv first."
|
|
115
|
+
elif install_method == "npx":
|
|
116
|
+
result = subprocess.run(["npx", "--version"], capture_output=True)
|
|
117
|
+
if result.returncode != 0:
|
|
118
|
+
return f"npx not available. Please install Node.js first."
|
|
119
|
+
except Exception as e:
|
|
120
|
+
return f"Error checking {install_method}: {e}"
|
|
121
|
+
|
|
122
|
+
# Mark as installed (basic implementation)
|
|
123
|
+
success = True # For now, assume installation succeeds
|
|
124
|
+
if success:
|
|
125
|
+
catalog_manager.mark_server_installed(server_name, True)
|
|
126
|
+
return f"Successfully installed {server_name}"
|
|
127
|
+
else:
|
|
128
|
+
return f"Failed to install {server_name}"
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@command()
|
|
132
|
+
async def mcp_catalog_install_and_run(server_name: str, context=None):
|
|
133
|
+
"""Install and run an MCP server from catalog
|
|
134
|
+
|
|
135
|
+
Example:
|
|
136
|
+
{ "mcp_catalog_install_and_run": { "server_name": "calculator" } }
|
|
137
|
+
"""
|
|
138
|
+
try:
|
|
139
|
+
# Get server info from catalog
|
|
140
|
+
server_info = catalog_manager.get_server_info(server_name)
|
|
141
|
+
if not server_info:
|
|
142
|
+
return f"Server {server_name} not found in catalog"
|
|
143
|
+
|
|
144
|
+
print(f"Installing and running {server_name}...")
|
|
145
|
+
|
|
146
|
+
# Create server configuration
|
|
147
|
+
print(f"Creating server configuration for {server_name}...")
|
|
148
|
+
|
|
149
|
+
server_config = MCPServer(
|
|
150
|
+
name=server_name,
|
|
151
|
+
description=server_info.get('description', f'MCP Server: {server_name}'),
|
|
152
|
+
command=server_info['command'],
|
|
153
|
+
args=server_info.get('args', []),
|
|
154
|
+
env=server_info.get('env', {}),
|
|
155
|
+
install_method=server_info.get('install_method', 'manual'),
|
|
156
|
+
install_package=server_info.get('install_package'),
|
|
157
|
+
auto_install=True,
|
|
158
|
+
installed=False
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
# Add server to manager
|
|
162
|
+
mcp_manager.add_server(server_name, server_config)
|
|
163
|
+
|
|
164
|
+
# Connect to the server
|
|
165
|
+
success = await mcp_manager.connect_server(server_name)
|
|
166
|
+
|
|
167
|
+
if success:
|
|
168
|
+
# Get final server status
|
|
169
|
+
catalog_manager.update_server_status()
|
|
170
|
+
|
|
171
|
+
# Import and call the refresh function to update dynamic commands
|
|
172
|
+
try:
|
|
173
|
+
from .mod import mcp_refresh_dynamic_commands
|
|
174
|
+
await mcp_refresh_dynamic_commands()
|
|
175
|
+
except Exception as e:
|
|
176
|
+
print(f"Warning: Could not refresh dynamic commands: {e}")
|
|
177
|
+
|
|
178
|
+
return f"Successfully installed and connected to {server_name}. MCP tools are now available as commands."
|
|
179
|
+
else:
|
|
180
|
+
return f"Installed {server_name} but failed to connect. Check server configuration."
|
|
181
|
+
|
|
182
|
+
except Exception as e:
|
|
183
|
+
print(f"Error in mcp_catalog_install_and_run: {e}")
|
|
184
|
+
return f"Error installing and running {server_name}: {str(e)}"
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
@command()
|
|
188
|
+
async def mcp_catalog_stop(server_name: str, context=None):
|
|
189
|
+
"""Stop an MCP server
|
|
190
|
+
|
|
191
|
+
Example:
|
|
192
|
+
{ "mcp_catalog_stop": { "server_name": "calculator" } }
|
|
193
|
+
"""
|
|
194
|
+
success = await mcp_manager.disconnect_server(server_name)
|
|
195
|
+
|
|
196
|
+
if success:
|
|
197
|
+
catalog_manager.update_server_status()
|
|
198
|
+
return f"Successfully stopped {server_name}"
|
|
199
|
+
else:
|
|
200
|
+
return f"Failed to stop {server_name}"
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
@command()
|
|
204
|
+
async def mcp_catalog_status(context=None):
|
|
205
|
+
"""Get status of all catalog servers
|
|
206
|
+
|
|
207
|
+
Example:
|
|
208
|
+
{ "mcp_catalog_status": {} }
|
|
209
|
+
"""
|
|
210
|
+
catalog = catalog_manager.update_server_status()
|
|
211
|
+
servers = catalog.get("servers", {})
|
|
212
|
+
|
|
213
|
+
status_summary = {
|
|
214
|
+
"total": len(servers),
|
|
215
|
+
"installed": 0,
|
|
216
|
+
"running": 0,
|
|
217
|
+
"available": 0
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
server_status = []
|
|
221
|
+
|
|
222
|
+
for name, server in servers.items():
|
|
223
|
+
installed = server.get("installed", False)
|
|
224
|
+
running = server.get("running", False)
|
|
225
|
+
|
|
226
|
+
if installed:
|
|
227
|
+
status_summary["installed"] += 1
|
|
228
|
+
if running:
|
|
229
|
+
status_summary["running"] += 1
|
|
230
|
+
if server.get("status") == "available":
|
|
231
|
+
status_summary["available"] += 1
|
|
232
|
+
|
|
233
|
+
server_status.append({
|
|
234
|
+
"name": name,
|
|
235
|
+
"display_name": server.get("display_name", name),
|
|
236
|
+
"installed": installed,
|
|
237
|
+
"running": running,
|
|
238
|
+
"category": server.get("category", "unknown")
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
return {
|
|
242
|
+
"summary": status_summary,
|
|
243
|
+
"servers": server_status
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
@command()
|
|
248
|
+
async def mcp_catalog_info(server_name: str, context=None):
|
|
249
|
+
"""Get detailed info about a catalog server
|
|
250
|
+
|
|
251
|
+
Example:
|
|
252
|
+
{ "mcp_catalog_info": { "server_name": "calculator" } }
|
|
253
|
+
"""
|
|
254
|
+
server_info = catalog_manager.get_server_info(server_name)
|
|
255
|
+
|
|
256
|
+
if not server_info:
|
|
257
|
+
return f"Server {server_name} not found in catalog"
|
|
258
|
+
|
|
259
|
+
# Update running status
|
|
260
|
+
running_status = catalog_manager.detect_running_servers()
|
|
261
|
+
server_info["running"] = running_status.get(server_name, False)
|
|
262
|
+
|
|
263
|
+
return server_info
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
@command()
|
|
267
|
+
async def mcp_catalog_categories(context=None):
|
|
268
|
+
"""Get list of server categories
|
|
269
|
+
|
|
270
|
+
Example:
|
|
271
|
+
{ "mcp_catalog_categories": {} }
|
|
272
|
+
"""
|
|
273
|
+
categories = catalog_manager.get_categories()
|
|
274
|
+
catalog = catalog_manager.load_catalog()
|
|
275
|
+
|
|
276
|
+
# Count servers per category
|
|
277
|
+
category_counts = {}
|
|
278
|
+
for server in catalog.get("servers", {}).values():
|
|
279
|
+
category = server.get("category", "unknown")
|
|
280
|
+
category_counts[category] = category_counts.get(category, 0) + 1
|
|
281
|
+
|
|
282
|
+
return {
|
|
283
|
+
"categories": categories,
|
|
284
|
+
"counts": category_counts
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
@command()
|
|
289
|
+
async def mcp_catalog_add_custom(server_info: Dict[str, Any], context=None):
|
|
290
|
+
"""Add a custom server to the catalog
|
|
291
|
+
|
|
292
|
+
Example:
|
|
293
|
+
{ "mcp_catalog_add_custom": {
|
|
294
|
+
"server_info": {
|
|
295
|
+
"name": "my_server",
|
|
296
|
+
"display_name": "My Custom Server",
|
|
297
|
+
"description": "Custom MCP server",
|
|
298
|
+
"command": "python",
|
|
299
|
+
"args": ["-m", "my_mcp_server"],
|
|
300
|
+
"category": "custom",
|
|
301
|
+
"install_method": "manual"
|
|
302
|
+
}
|
|
303
|
+
}}
|
|
304
|
+
"""
|
|
305
|
+
success = catalog_manager.add_custom_server(server_info)
|
|
306
|
+
|
|
307
|
+
if success:
|
|
308
|
+
return f"Successfully added custom server {server_info.get('name')}"
|
|
309
|
+
else:
|
|
310
|
+
return "Failed to add custom server - missing required fields"
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
@command()
|
|
314
|
+
async def mcp_catalog_refresh(context=None):
|
|
315
|
+
"""Refresh catalog and update server status
|
|
316
|
+
|
|
317
|
+
Example:
|
|
318
|
+
{ "mcp_catalog_refresh": {} }
|
|
319
|
+
"""
|
|
320
|
+
catalog = catalog_manager.update_server_status()
|
|
321
|
+
running_count = sum(1 for server in catalog.get("servers", {}).values()
|
|
322
|
+
if server.get("running", False))
|
|
323
|
+
|
|
324
|
+
return {
|
|
325
|
+
"message": "Catalog refreshed",
|
|
326
|
+
"total_servers": len(catalog.get("servers", {})),
|
|
327
|
+
"running_servers": running_count
|
|
328
|
+
}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import psutil
|
|
4
|
+
import subprocess
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Dict, List, Optional, Any
|
|
7
|
+
|
|
8
|
+
class MCPCatalogManager:
|
|
9
|
+
"""Manages MCP server catalog and process detection"""
|
|
10
|
+
|
|
11
|
+
def __init__(self, plugin_dir: str = None, working_dir: str = None):
|
|
12
|
+
# Default plugin_dir to the directory containing this module
|
|
13
|
+
if plugin_dir is None:
|
|
14
|
+
plugin_dir = Path(__file__).parent.parent.parent
|
|
15
|
+
self.plugin_dir = Path(plugin_dir)
|
|
16
|
+
|
|
17
|
+
# Use process working directory for data files
|
|
18
|
+
if working_dir is None:
|
|
19
|
+
working_dir = os.getcwd()
|
|
20
|
+
self.working_dir = Path(working_dir)
|
|
21
|
+
|
|
22
|
+
self.catalog_file = self.working_dir / "data" / "mcp" / "servers.json"
|
|
23
|
+
self.default_file = Path(__file__).parent / "data" / "default_mcp_servers.json"
|
|
24
|
+
self.ensure_catalog_exists()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def ensure_catalog_exists(self):
|
|
28
|
+
"""Ensure catalog file exists, create from default if needed"""
|
|
29
|
+
if not self.catalog_file.exists():
|
|
30
|
+
if self.default_file.exists():
|
|
31
|
+
self.init_catalog_from_default()
|
|
32
|
+
else:
|
|
33
|
+
self.create_empty_catalog()
|
|
34
|
+
|
|
35
|
+
def init_catalog_from_default(self):
|
|
36
|
+
"""Initialize catalog from default servers"""
|
|
37
|
+
# Ensure the target directory exists
|
|
38
|
+
self.catalog_file.parent.mkdir(parents=True, exist_ok=True)
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
with open(self.default_file, 'r') as f:
|
|
42
|
+
default_servers = json.load(f)
|
|
43
|
+
|
|
44
|
+
catalog = {
|
|
45
|
+
"_metadata": {
|
|
46
|
+
"version": "1.0.0",
|
|
47
|
+
"last_updated": "2025-06-02",
|
|
48
|
+
"description": "MCP Server Catalog for MindRoot",
|
|
49
|
+
"categories": ["utilities", "system", "development", "search", "database", "communication", "cloud", "automation", "ai"]
|
|
50
|
+
},
|
|
51
|
+
"servers": {}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
for name, server in default_servers.items():
|
|
55
|
+
server["status"] = "available"
|
|
56
|
+
server["installed"] = False
|
|
57
|
+
server["running"] = False
|
|
58
|
+
catalog["servers"][name] = server
|
|
59
|
+
|
|
60
|
+
with open(self.catalog_file, 'w') as f:
|
|
61
|
+
json.dump(catalog, f, indent=2)
|
|
62
|
+
|
|
63
|
+
except Exception as e:
|
|
64
|
+
print(f"Error initializing catalog: {e}")
|
|
65
|
+
self.create_empty_catalog()
|
|
66
|
+
|
|
67
|
+
def create_empty_catalog(self):
|
|
68
|
+
"""Create empty catalog structure"""
|
|
69
|
+
catalog = {
|
|
70
|
+
"_metadata": {
|
|
71
|
+
"version": "1.0.0",
|
|
72
|
+
"last_updated": "2025-06-02",
|
|
73
|
+
"description": "MCP Server Catalog for MindRoot",
|
|
74
|
+
"categories": []
|
|
75
|
+
},
|
|
76
|
+
"servers": {}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
self.catalog_file.parent.mkdir(parents=True, exist_ok=True)
|
|
80
|
+
with open(self.catalog_file, 'w') as f:
|
|
81
|
+
json.dump(catalog, f, indent=2)
|
|
82
|
+
|
|
83
|
+
def load_catalog(self) -> Dict[str, Any]:
|
|
84
|
+
"""Load the server catalog"""
|
|
85
|
+
try:
|
|
86
|
+
with open(self.catalog_file, 'r') as f:
|
|
87
|
+
return json.load(f)
|
|
88
|
+
except Exception as e:
|
|
89
|
+
print(f"Error loading catalog: {e}")
|
|
90
|
+
return {"_metadata": {}, "servers": {}}
|
|
91
|
+
|
|
92
|
+
def save_catalog(self, catalog: Dict[str, Any]):
|
|
93
|
+
"""Save the server catalog"""
|
|
94
|
+
try:
|
|
95
|
+
with open(self.catalog_file, 'w') as f:
|
|
96
|
+
json.dump(catalog, f, indent=2)
|
|
97
|
+
except Exception as e:
|
|
98
|
+
print(f"Error saving catalog: {e}")
|
|
99
|
+
|
|
100
|
+
def get_running_processes(self) -> List[Dict[str, Any]]:
|
|
101
|
+
"""Get list of running processes with details"""
|
|
102
|
+
processes = []
|
|
103
|
+
try:
|
|
104
|
+
for proc in psutil.process_iter(['pid', 'name', 'cmdline']):
|
|
105
|
+
try:
|
|
106
|
+
processes.append({
|
|
107
|
+
'pid': proc.info['pid'],
|
|
108
|
+
'name': proc.info['name'],
|
|
109
|
+
'cmdline': proc.info['cmdline'] or []
|
|
110
|
+
})
|
|
111
|
+
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
|
112
|
+
continue
|
|
113
|
+
except Exception as e:
|
|
114
|
+
print(f"Error getting processes: {e}")
|
|
115
|
+
|
|
116
|
+
return processes
|
|
117
|
+
|
|
118
|
+
def detect_running_servers(self) -> Dict[str, bool]:
|
|
119
|
+
"""Detect which MCP servers are currently running"""
|
|
120
|
+
catalog = self.load_catalog()
|
|
121
|
+
running_status = {}
|
|
122
|
+
processes = self.get_running_processes()
|
|
123
|
+
|
|
124
|
+
for server_name, server_info in catalog.get("servers", {}).items():
|
|
125
|
+
is_running = False
|
|
126
|
+
process_names = server_info.get("process_names", [])
|
|
127
|
+
|
|
128
|
+
for proc in processes:
|
|
129
|
+
# Check process name
|
|
130
|
+
if proc['name'] in process_names:
|
|
131
|
+
is_running = True
|
|
132
|
+
break
|
|
133
|
+
|
|
134
|
+
# Check command line for MCP server patterns
|
|
135
|
+
cmdline_str = ' '.join(proc['cmdline'])
|
|
136
|
+
for process_name in process_names:
|
|
137
|
+
if process_name in cmdline_str:
|
|
138
|
+
is_running = True
|
|
139
|
+
break
|
|
140
|
+
|
|
141
|
+
# Check for specific patterns
|
|
142
|
+
if server_name in cmdline_str or server_info.get('install_package', '') in cmdline_str:
|
|
143
|
+
is_running = True
|
|
144
|
+
break
|
|
145
|
+
|
|
146
|
+
if is_running:
|
|
147
|
+
break
|
|
148
|
+
|
|
149
|
+
running_status[server_name] = is_running
|
|
150
|
+
|
|
151
|
+
return running_status
|
|
152
|
+
|
|
153
|
+
def update_server_status(self):
|
|
154
|
+
"""Update running status for all servers in catalog"""
|
|
155
|
+
catalog = self.load_catalog()
|
|
156
|
+
running_status = self.detect_running_servers()
|
|
157
|
+
|
|
158
|
+
for server_name, is_running in running_status.items():
|
|
159
|
+
if server_name in catalog.get("servers", {}):
|
|
160
|
+
catalog["servers"][server_name]["running"] = is_running
|
|
161
|
+
|
|
162
|
+
self.save_catalog(catalog)
|
|
163
|
+
return catalog
|
|
164
|
+
|
|
165
|
+
def get_servers_by_category(self, category: str = None) -> Dict[str, Any]:
|
|
166
|
+
"""Get servers filtered by category"""
|
|
167
|
+
catalog = self.load_catalog()
|
|
168
|
+
servers = catalog.get("servers", {})
|
|
169
|
+
|
|
170
|
+
if category is None:
|
|
171
|
+
return servers
|
|
172
|
+
|
|
173
|
+
filtered = {}
|
|
174
|
+
for name, server in servers.items():
|
|
175
|
+
if server.get("category") == category:
|
|
176
|
+
filtered[name] = server
|
|
177
|
+
|
|
178
|
+
return filtered
|
|
179
|
+
|
|
180
|
+
def get_server_info(self, server_name: str) -> Optional[Dict[str, Any]]:
|
|
181
|
+
"""Get detailed info for a specific server"""
|
|
182
|
+
catalog = self.load_catalog()
|
|
183
|
+
return catalog.get("servers", {}).get(server_name)
|
|
184
|
+
|
|
185
|
+
def mark_server_installed(self, server_name: str, installed: bool = True):
|
|
186
|
+
"""Mark a server as installed or not"""
|
|
187
|
+
catalog = self.load_catalog()
|
|
188
|
+
if server_name in catalog.get("servers", {}):
|
|
189
|
+
catalog["servers"][server_name]["installed"] = installed
|
|
190
|
+
self.save_catalog(catalog)
|
|
191
|
+
|
|
192
|
+
def add_custom_server(self, server_info: Dict[str, Any]):
|
|
193
|
+
"""Add a custom server to the catalog"""
|
|
194
|
+
catalog = self.load_catalog()
|
|
195
|
+
server_name = server_info.get("name")
|
|
196
|
+
|
|
197
|
+
if server_name:
|
|
198
|
+
# Add default fields if missing
|
|
199
|
+
server_info.setdefault("status", "custom")
|
|
200
|
+
server_info.setdefault("installed", False)
|
|
201
|
+
server_info.setdefault("running", False)
|
|
202
|
+
server_info.setdefault("category", "custom")
|
|
203
|
+
server_info.setdefault("tags", [])
|
|
204
|
+
|
|
205
|
+
catalog["servers"][server_name] = server_info
|
|
206
|
+
self.save_catalog(catalog)
|
|
207
|
+
return True
|
|
208
|
+
|
|
209
|
+
return False
|
|
210
|
+
|
|
211
|
+
def remove_server(self, server_name: str):
|
|
212
|
+
"""Remove a server from the catalog"""
|
|
213
|
+
catalog = self.load_catalog()
|
|
214
|
+
if server_name in catalog.get("servers", {}):
|
|
215
|
+
del catalog["servers"][server_name]
|
|
216
|
+
self.save_catalog(catalog)
|
|
217
|
+
return True
|
|
218
|
+
return False
|
|
219
|
+
|
|
220
|
+
def search_servers(self, query: str) -> Dict[str, Any]:
|
|
221
|
+
"""Search servers by name, description, or tags"""
|
|
222
|
+
catalog = self.load_catalog()
|
|
223
|
+
servers = catalog.get("servers", {})
|
|
224
|
+
results = {}
|
|
225
|
+
|
|
226
|
+
query_lower = query.lower()
|
|
227
|
+
|
|
228
|
+
for name, server in servers.items():
|
|
229
|
+
# Search in name
|
|
230
|
+
if query_lower in name.lower():
|
|
231
|
+
results[name] = server
|
|
232
|
+
continue
|
|
233
|
+
|
|
234
|
+
# Search in display name
|
|
235
|
+
if query_lower in server.get("display_name", "").lower():
|
|
236
|
+
results[name] = server
|
|
237
|
+
continue
|
|
238
|
+
|
|
239
|
+
# Search in description
|
|
240
|
+
if query_lower in server.get("description", "").lower():
|
|
241
|
+
results[name] = server
|
|
242
|
+
continue
|
|
243
|
+
|
|
244
|
+
# Search in tags
|
|
245
|
+
tags = server.get("tags", [])
|
|
246
|
+
if any(query_lower in tag.lower() for tag in tags):
|
|
247
|
+
results[name] = server
|
|
248
|
+
continue
|
|
249
|
+
|
|
250
|
+
return results
|
|
251
|
+
|
|
252
|
+
def get_categories(self) -> List[str]:
|
|
253
|
+
"""Get list of all categories"""
|
|
254
|
+
catalog = self.load_catalog()
|
|
255
|
+
categories = set(catalog.get("_metadata", {}).get("categories", []))
|
|
256
|
+
|
|
257
|
+
# Add categories from servers
|
|
258
|
+
for server in catalog.get("servers", {}).values():
|
|
259
|
+
category = server.get("category")
|
|
260
|
+
if category:
|
|
261
|
+
categories.add(category)
|
|
262
|
+
|
|
263
|
+
return sorted(list(categories))
|