mindroot 9.3.0__py3-none-any.whl → 9.5.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 +63 -0
- 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-old.js +385 -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 +857 -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 +6 -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.5.0.dist-info}/METADATA +7 -2
- {mindroot-9.3.0.dist-info → mindroot-9.5.0.dist-info}/RECORD +144 -112
- 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.5.0.dist-info}/WHEEL +0 -0
- {mindroot-9.3.0.dist-info → mindroot-9.5.0.dist-info}/entry_points.txt +0 -0
- {mindroot-9.3.0.dist-info → mindroot-9.5.0.dist-info}/licenses/LICENSE +0 -0
- {mindroot-9.3.0.dist-info → mindroot-9.5.0.dist-info}/top_level.txt +0 -0
|
@@ -5,7 +5,12 @@ from lib.plugins.installation import plugin_install
|
|
|
5
5
|
from lib.providers.missing import get_missing_commands
|
|
6
6
|
from lib.plugins.mapping import get_command_plugin_mapping
|
|
7
7
|
from pathlib import Path
|
|
8
|
-
from lib.plugins.installation import install_recommended_plugins
|
|
8
|
+
from lib.plugins.installation import install_recommended_plugins, download_github_files
|
|
9
|
+
from lib.plugins.manifest import update_plugin_manifest
|
|
10
|
+
# For Server-Sent Events (streamed logs)
|
|
11
|
+
from sse_starlette.sse import EventSourceResponse
|
|
12
|
+
from lib.streamcmd import stream_command_as_events
|
|
13
|
+
import contextlib, io, json, asyncio, sys
|
|
9
14
|
|
|
10
15
|
router = APIRouter()
|
|
11
16
|
|
|
@@ -20,6 +25,121 @@ async def missing_commands(agent_name: str):
|
|
|
20
25
|
raise HTTPException(status_code=500, detail=result["error"])
|
|
21
26
|
return result
|
|
22
27
|
|
|
28
|
+
# ------------------------------------------------------------------
|
|
29
|
+
# NEW: Stream installation of recommended plugins with live logs
|
|
30
|
+
# ------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
@router.get("/admin/stream-install-recommended-plugins/{agent_name}", response_class=EventSourceResponse)
|
|
33
|
+
async def stream_install_recommended_plugins(agent_name: str):
|
|
34
|
+
"""
|
|
35
|
+
SSE endpoint that installs all recommended plugins for `agent_name`
|
|
36
|
+
and streams stdout/stderr lines back to the browser. It finishes by
|
|
37
|
+
sending a JSON summary and the literal string 'END'.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
async def event_generator():
|
|
41
|
+
try:
|
|
42
|
+
# Load agent data
|
|
43
|
+
agent_path = f"data/agents/local/{agent_name}/agent.json"
|
|
44
|
+
if not os.path.exists(agent_path):
|
|
45
|
+
agent_path = f"data/agents/shared/{agent_name}/agent.json"
|
|
46
|
+
|
|
47
|
+
if not os.path.exists(agent_path):
|
|
48
|
+
yield f"Error: Agent {agent_name} not found\n"
|
|
49
|
+
yield "END\n"
|
|
50
|
+
return
|
|
51
|
+
|
|
52
|
+
with open(agent_path, 'r') as f:
|
|
53
|
+
agent_data = json.load(f)
|
|
54
|
+
|
|
55
|
+
# Get recommended plugins
|
|
56
|
+
recommended_plugins = agent_data.get('recommended_plugins', agent_data.get('required_plugins', []))
|
|
57
|
+
|
|
58
|
+
if not recommended_plugins:
|
|
59
|
+
yield "No recommended plugins found for this agent\n"
|
|
60
|
+
yield "END\n"
|
|
61
|
+
return
|
|
62
|
+
|
|
63
|
+
yield f"Installing {len(recommended_plugins)} recommended plugins for {agent_name}...\n"
|
|
64
|
+
|
|
65
|
+
results = []
|
|
66
|
+
|
|
67
|
+
# Install each plugin and stream the output
|
|
68
|
+
for plugin_source in recommended_plugins:
|
|
69
|
+
plugin_name = plugin_source.split('/')[-1]
|
|
70
|
+
|
|
71
|
+
yield f"\n=== Installing {plugin_name} ===\n"
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
# Check if already installed
|
|
75
|
+
import pkg_resources
|
|
76
|
+
try:
|
|
77
|
+
pkg_resources.get_distribution(plugin_name)
|
|
78
|
+
yield f"{plugin_name} is already installed\n"
|
|
79
|
+
results.append({"plugin": plugin_name, "status": "already_installed"})
|
|
80
|
+
continue
|
|
81
|
+
except pkg_resources.DistributionNotFound:
|
|
82
|
+
pass
|
|
83
|
+
|
|
84
|
+
# Determine installation source
|
|
85
|
+
if '/' in plugin_source: # GitHub format
|
|
86
|
+
yield f"Installing from GitHub: {plugin_source}\n"
|
|
87
|
+
|
|
88
|
+
# Download and extract
|
|
89
|
+
plugin_dir, _, plugin_info = download_github_files(plugin_source)
|
|
90
|
+
|
|
91
|
+
# Stream the pip install
|
|
92
|
+
cmd = [sys.executable, '-m', 'pip', 'install', '-e', plugin_dir, '-v']
|
|
93
|
+
async for event in stream_command_as_events(cmd):
|
|
94
|
+
if event.get('event') == 'message':
|
|
95
|
+
yield event['data'] + "\n"
|
|
96
|
+
elif event.get('event') == 'warning':
|
|
97
|
+
yield "WARNING: " + event['data'] + "\n"
|
|
98
|
+
elif event.get('event') == 'error':
|
|
99
|
+
yield "ERROR: " + event['data'] + "\n"
|
|
100
|
+
elif event.get('event') == 'complete':
|
|
101
|
+
# Update the plugin manifest after successful installation
|
|
102
|
+
update_plugin_manifest(
|
|
103
|
+
plugin_info['name'],
|
|
104
|
+
'github',
|
|
105
|
+
os.path.abspath(plugin_dir),
|
|
106
|
+
remote_source=plugin_source,
|
|
107
|
+
version=plugin_info.get('version', '0.0.1'),
|
|
108
|
+
metadata=plugin_info
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
results.append({"plugin": plugin_name, "status": "success", "source": "github", "source_path": plugin_source})
|
|
112
|
+
else:
|
|
113
|
+
# PyPI installation
|
|
114
|
+
yield f"Installing from PyPI: {plugin_name}\n"
|
|
115
|
+
cmd = [sys.executable, '-m', 'pip', 'install', plugin_name, '-v']
|
|
116
|
+
async for event in stream_command_as_events(cmd):
|
|
117
|
+
if event.get('event') == 'message':
|
|
118
|
+
yield event['data'] + "\n"
|
|
119
|
+
elif event.get('event') == 'warning':
|
|
120
|
+
yield "WARNING: " + event['data'] + "\n"
|
|
121
|
+
elif event.get('event') == 'error':
|
|
122
|
+
yield "ERROR: " + event['data'] + "\n"
|
|
123
|
+
elif event.get('event') == 'complete':
|
|
124
|
+
# Update the plugin manifest for PyPI install
|
|
125
|
+
update_plugin_manifest(plugin_name, 'pypi', None)
|
|
126
|
+
|
|
127
|
+
results.append({"plugin": plugin_name, "status": "success", "source": "pypi"})
|
|
128
|
+
|
|
129
|
+
except Exception as e:
|
|
130
|
+
yield f"ERROR: Failed to install {plugin_name}: {str(e)}\n"
|
|
131
|
+
results.append({"plugin": plugin_name, "status": "error", "message": str(e)})
|
|
132
|
+
|
|
133
|
+
# Send results and END
|
|
134
|
+
yield "\n" + json.dumps({"results": results}) + "\n"
|
|
135
|
+
yield "END"
|
|
136
|
+
|
|
137
|
+
except Exception as e:
|
|
138
|
+
yield f"ERROR: {str(e)}\n"
|
|
139
|
+
yield "END"
|
|
140
|
+
|
|
141
|
+
return EventSourceResponse(event_generator())
|
|
142
|
+
|
|
23
143
|
@router.get("/admin/command-plugin-mapping")
|
|
24
144
|
async def command_plugin_mapping():
|
|
25
145
|
"""Get mapping of commands to plugins that provide them."""
|
|
@@ -159,3 +279,118 @@ async def install_agent_recommended_plugins(agent_name: str):
|
|
|
159
279
|
if "error" in result:
|
|
160
280
|
raise HTTPException(status_code=500, detail=result["error"])
|
|
161
281
|
return result
|
|
282
|
+
|
|
283
|
+
# ------------------------------------------------------------------
|
|
284
|
+
# NEW: Stream installation of recommended plugins with live logs
|
|
285
|
+
# ------------------------------------------------------------------
|
|
286
|
+
|
|
287
|
+
@router.get("/admin/stream-install-recommended-plugins/{agent_name}", response_class=EventSourceResponse)
|
|
288
|
+
async def stream_install_recommended_plugins(agent_name: str):
|
|
289
|
+
"""
|
|
290
|
+
SSE endpoint that installs all recommended plugins for `agent_name`
|
|
291
|
+
and streams stdout/stderr lines back to the browser. It finishes by
|
|
292
|
+
sending a JSON summary and the literal string 'END'.
|
|
293
|
+
"""
|
|
294
|
+
|
|
295
|
+
async def event_generator():
|
|
296
|
+
try:
|
|
297
|
+
# Load agent data
|
|
298
|
+
agent_path = f"data/agents/local/{agent_name}/agent.json"
|
|
299
|
+
if not os.path.exists(agent_path):
|
|
300
|
+
agent_path = f"data/agents/shared/{agent_name}/agent.json"
|
|
301
|
+
|
|
302
|
+
if not os.path.exists(agent_path):
|
|
303
|
+
yield f"Error: Agent {agent_name} not found\n"
|
|
304
|
+
yield "END\n"
|
|
305
|
+
return
|
|
306
|
+
|
|
307
|
+
with open(agent_path, 'r') as f:
|
|
308
|
+
agent_data = json.load(f)
|
|
309
|
+
|
|
310
|
+
# Get recommended plugins
|
|
311
|
+
recommended_plugins = agent_data.get('recommended_plugins', agent_data.get('required_plugins', []))
|
|
312
|
+
|
|
313
|
+
if not recommended_plugins:
|
|
314
|
+
yield "No recommended plugins found for this agent\n"
|
|
315
|
+
yield "END\n"
|
|
316
|
+
return
|
|
317
|
+
|
|
318
|
+
yield f"Installing {len(recommended_plugins)} recommended plugins for {agent_name}...\n"
|
|
319
|
+
|
|
320
|
+
results = []
|
|
321
|
+
|
|
322
|
+
# Install each plugin and stream the output
|
|
323
|
+
for plugin_source in recommended_plugins:
|
|
324
|
+
plugin_name = plugin_source.split('/')[-1]
|
|
325
|
+
|
|
326
|
+
yield f"\n=== Installing {plugin_name} ===\n"
|
|
327
|
+
|
|
328
|
+
try:
|
|
329
|
+
# Check if already installed
|
|
330
|
+
import pkg_resources
|
|
331
|
+
try:
|
|
332
|
+
pkg_resources.get_distribution(plugin_name)
|
|
333
|
+
yield f"{plugin_name} is already installed\n"
|
|
334
|
+
results.append({"plugin": plugin_name, "status": "already_installed"})
|
|
335
|
+
continue
|
|
336
|
+
except pkg_resources.DistributionNotFound:
|
|
337
|
+
pass
|
|
338
|
+
|
|
339
|
+
# Determine installation source
|
|
340
|
+
if '/' in plugin_source: # GitHub format
|
|
341
|
+
yield f"Installing from GitHub: {plugin_source}\n"
|
|
342
|
+
|
|
343
|
+
# Download and extract
|
|
344
|
+
plugin_dir, _, plugin_info = download_github_files(plugin_source)
|
|
345
|
+
|
|
346
|
+
# Stream the pip install
|
|
347
|
+
cmd = [sys.executable, '-m', 'pip', 'install', '-e', plugin_dir, '-v']
|
|
348
|
+
async for event in stream_command_as_events(cmd):
|
|
349
|
+
if event.get('event') == 'message':
|
|
350
|
+
yield event['data'] + "\n"
|
|
351
|
+
elif event.get('event') == 'warning':
|
|
352
|
+
yield "WARNING: " + event['data'] + "\n"
|
|
353
|
+
elif event.get('event') == 'error':
|
|
354
|
+
yield "ERROR: " + event['data'] + "\n"
|
|
355
|
+
elif event.get('event') == 'complete':
|
|
356
|
+
# Update the plugin manifest after successful installation
|
|
357
|
+
update_plugin_manifest(
|
|
358
|
+
plugin_info['name'],
|
|
359
|
+
'github',
|
|
360
|
+
os.path.abspath(plugin_dir),
|
|
361
|
+
remote_source=plugin_source,
|
|
362
|
+
version=plugin_info.get('version', '0.0.1'),
|
|
363
|
+
metadata=plugin_info
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
results.append({"plugin": plugin_name, "status": "success", "source": "github", "source_path": plugin_source})
|
|
367
|
+
else:
|
|
368
|
+
# PyPI installation
|
|
369
|
+
yield f"Installing from PyPI: {plugin_name}\n"
|
|
370
|
+
cmd = [sys.executable, '-m', 'pip', 'install', plugin_name, '-v']
|
|
371
|
+
async for event in stream_command_as_events(cmd):
|
|
372
|
+
if event.get('event') == 'message':
|
|
373
|
+
yield event['data'] + "\n"
|
|
374
|
+
elif event.get('event') == 'warning':
|
|
375
|
+
yield "WARNING: " + event['data'] + "\n"
|
|
376
|
+
elif event.get('event') == 'error':
|
|
377
|
+
yield "ERROR: " + event['data'] + "\n"
|
|
378
|
+
elif event.get('event') == 'complete':
|
|
379
|
+
# Update the plugin manifest for PyPI install
|
|
380
|
+
update_plugin_manifest(plugin_name, 'pypi', None)
|
|
381
|
+
|
|
382
|
+
results.append({"plugin": plugin_name, "status": "success", "source": "pypi"})
|
|
383
|
+
|
|
384
|
+
except Exception as e:
|
|
385
|
+
yield f"ERROR: Failed to install {plugin_name}: {str(e)}\n"
|
|
386
|
+
results.append({"plugin": plugin_name, "status": "error", "message": str(e)})
|
|
387
|
+
|
|
388
|
+
# Send results and END
|
|
389
|
+
yield "\n" + json.dumps({"results": results}) + "\n"
|
|
390
|
+
yield "END"
|
|
391
|
+
|
|
392
|
+
except Exception as e:
|
|
393
|
+
yield f"ERROR: {str(e)}\n"
|
|
394
|
+
yield "END"
|
|
395
|
+
|
|
396
|
+
return EventSourceResponse(event_generator())
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
from fastapi import APIRouter, HTTPException
|
|
2
|
+
from typing import Optional
|
|
3
|
+
from lib.route_decorators import requires_role
|
|
4
|
+
|
|
5
|
+
# Import MCP components - use the actual MCP system
|
|
6
|
+
try:
|
|
7
|
+
from mindroot.coreplugins.mcp.mod import mcp_manager, MCPServer
|
|
8
|
+
from mindroot.coreplugins.mcp.catalog_manager import MCPCatalogManager
|
|
9
|
+
except ImportError:
|
|
10
|
+
# Mock objects if MCP plugin is not fully installed, to prevent startup crash
|
|
11
|
+
mcp_manager = None
|
|
12
|
+
MCPServer = None
|
|
13
|
+
MCPCatalogManager = None
|
|
14
|
+
|
|
15
|
+
# Create router with admin role requirement
|
|
16
|
+
router = APIRouter(
|
|
17
|
+
dependencies=[requires_role('admin')]
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
# --- MCP Catalog/Directory Routes ---
|
|
21
|
+
|
|
22
|
+
@router.get("/mcp/catalog")
|
|
23
|
+
async def get_mcp_catalog():
|
|
24
|
+
"""Get the MCP server catalog/directory."""
|
|
25
|
+
if not MCPCatalogManager:
|
|
26
|
+
raise HTTPException(status_code=501, detail="MCP Catalog not available")
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
catalog_manager = MCPCatalogManager()
|
|
30
|
+
catalog = catalog_manager.load_catalog()
|
|
31
|
+
|
|
32
|
+
# Update running status
|
|
33
|
+
catalog_manager.update_server_status()
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
"success": True,
|
|
37
|
+
"data": catalog
|
|
38
|
+
}
|
|
39
|
+
except Exception as e:
|
|
40
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
41
|
+
|
|
42
|
+
@router.get("/mcp/catalog/search")
|
|
43
|
+
async def search_mcp_catalog(query: str = "", category: Optional[str] = None):
|
|
44
|
+
"""Search the MCP server catalog."""
|
|
45
|
+
if not MCPCatalogManager:
|
|
46
|
+
raise HTTPException(status_code=501, detail="MCP Catalog not available")
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
catalog_manager = MCPCatalogManager()
|
|
50
|
+
|
|
51
|
+
if query:
|
|
52
|
+
results = catalog_manager.search_servers(query)
|
|
53
|
+
elif category:
|
|
54
|
+
results = catalog_manager.get_servers_by_category(category)
|
|
55
|
+
else:
|
|
56
|
+
catalog = catalog_manager.load_catalog()
|
|
57
|
+
results = catalog.get("servers", {})
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
"success": True,
|
|
61
|
+
"data": results
|
|
62
|
+
}
|
|
63
|
+
except Exception as e:
|
|
64
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
65
|
+
|
|
66
|
+
@router.get("/mcp/catalog/categories")
|
|
67
|
+
async def get_mcp_categories():
|
|
68
|
+
"""Get available MCP server categories."""
|
|
69
|
+
if not MCPCatalogManager:
|
|
70
|
+
raise HTTPException(status_code=501, detail="MCP Catalog not available")
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
catalog_manager = MCPCatalogManager()
|
|
74
|
+
categories = catalog_manager.get_categories()
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
"success": True,
|
|
78
|
+
"data": categories
|
|
79
|
+
}
|
|
80
|
+
except Exception as e:
|
|
81
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
82
|
+
|
|
83
|
+
@router.post("/mcp/catalog/install")
|
|
84
|
+
async def install_from_catalog(server_name: str):
|
|
85
|
+
"""Install an MCP server from the catalog."""
|
|
86
|
+
if not MCPCatalogManager or not mcp_manager:
|
|
87
|
+
raise HTTPException(status_code=501, detail="MCP system not available")
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
catalog_manager = MCPCatalogManager()
|
|
91
|
+
server_info = catalog_manager.get_server_info(server_name)
|
|
92
|
+
|
|
93
|
+
if not server_info:
|
|
94
|
+
raise HTTPException(status_code=404, detail=f"MCP server '{server_name}' not found in catalog")
|
|
95
|
+
|
|
96
|
+
# Create MCPServer object from catalog info
|
|
97
|
+
server = MCPServer(
|
|
98
|
+
name=server_name,
|
|
99
|
+
description=server_info.get("description", ""),
|
|
100
|
+
command=server_info.get("command", ""),
|
|
101
|
+
args=server_info.get("args", []),
|
|
102
|
+
env=server_info.get("env", {}),
|
|
103
|
+
transport=server_info.get("transport", "stdio"),
|
|
104
|
+
url=server_info.get("url")
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
# Add to MCP manager
|
|
108
|
+
mcp_manager.add_server(server_name, server)
|
|
109
|
+
|
|
110
|
+
# Mark as installed in catalog
|
|
111
|
+
catalog_manager.mark_server_installed(server_name, True)
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
"success": True,
|
|
115
|
+
"message": f"MCP server '{server_name}' installed from catalog successfully."
|
|
116
|
+
}
|
|
117
|
+
except Exception as e:
|
|
118
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
119
|
+
|
|
120
|
+
@router.get("/mcp/catalog/server/{server_name}")
|
|
121
|
+
async def get_catalog_server_info(server_name: str):
|
|
122
|
+
"""Get detailed information about a specific server from the catalog."""
|
|
123
|
+
if not MCPCatalogManager:
|
|
124
|
+
raise HTTPException(status_code=501, detail="MCP Catalog not available")
|
|
125
|
+
|
|
126
|
+
try:
|
|
127
|
+
catalog_manager = MCPCatalogManager()
|
|
128
|
+
server_info = catalog_manager.get_server_info(server_name)
|
|
129
|
+
|
|
130
|
+
if not server_info:
|
|
131
|
+
raise HTTPException(status_code=404, detail=f"MCP server '{server_name}' not found in catalog")
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
"success": True,
|
|
135
|
+
"data": server_info
|
|
136
|
+
}
|
|
137
|
+
except Exception as e:
|
|
138
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
139
|
+
|
|
140
|
+
@router.post("/mcp/catalog/refresh")
|
|
141
|
+
async def refresh_catalog_status():
|
|
142
|
+
"""Refresh the running status of all servers in the catalog."""
|
|
143
|
+
if not MCPCatalogManager:
|
|
144
|
+
raise HTTPException(status_code=501, detail="MCP Catalog not available")
|
|
145
|
+
|
|
146
|
+
try:
|
|
147
|
+
catalog_manager = MCPCatalogManager()
|
|
148
|
+
updated_catalog = catalog_manager.update_server_status()
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
"success": True,
|
|
152
|
+
"message": "Catalog status refreshed successfully.",
|
|
153
|
+
"data": updated_catalog
|
|
154
|
+
}
|
|
155
|
+
except Exception as e:
|
|
156
|
+
raise HTTPException(status_code=500, detail=str(e))
|