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.
Files changed (183) hide show
  1. mindroot/coreplugins/admin/__init__.py +3 -1
  2. mindroot/coreplugins/admin/agent_router.py +250 -7
  3. mindroot/coreplugins/admin/asset_manager.py +164 -0
  4. mindroot/coreplugins/admin/command_router.py +236 -1
  5. mindroot/coreplugins/admin/mcp_catalog_routes.py +156 -0
  6. mindroot/coreplugins/admin/mcp_publish_routes.py +450 -0
  7. mindroot/coreplugins/admin/mcp_registry_routes.py +495 -0
  8. mindroot/coreplugins/admin/mcp_routes.py +216 -0
  9. mindroot/coreplugins/admin/mod.py +62 -0
  10. mindroot/coreplugins/admin/oauth_callback_router.py +84 -0
  11. mindroot/coreplugins/admin/persona_handler.py +15 -6
  12. mindroot/coreplugins/admin/persona_router.py +158 -2
  13. mindroot/coreplugins/admin/plugin_manager.py +105 -9
  14. mindroot/coreplugins/admin/plugin_router_fixed.py +23 -0
  15. mindroot/coreplugins/admin/plugin_router_new_not_working.py +145 -0
  16. mindroot/coreplugins/admin/plugin_routes.py +114 -0
  17. mindroot/coreplugins/admin/registry_settings_routes.py +140 -0
  18. mindroot/coreplugins/admin/router.py +116 -15
  19. mindroot/coreplugins/admin/service_models.py +1 -1
  20. mindroot/coreplugins/admin/settings_router.py +1 -0
  21. mindroot/coreplugins/admin/static/css/admin-custom.css +357 -2
  22. mindroot/coreplugins/admin/static/css/dark.css +1 -0
  23. mindroot/coreplugins/admin/static/css/default.css +4 -0
  24. mindroot/coreplugins/admin/static/js/about-info.js +367 -0
  25. mindroot/coreplugins/admin/static/js/agent-form.js +83 -3
  26. mindroot/coreplugins/admin/static/js/api-key-script.js +307 -0
  27. mindroot/coreplugins/admin/static/js/mcp-manager.js +348 -0
  28. mindroot/coreplugins/admin/static/js/mcp-publisher.js +780 -0
  29. mindroot/coreplugins/admin/static/js/persona-editor.js +34 -5
  30. mindroot/coreplugins/admin/static/js/plugin-toggle.js +1 -1
  31. mindroot/coreplugins/admin/static/js/recommended-plugin-install.js +63 -0
  32. mindroot/coreplugins/admin/static/js/registry-auth-section.js +132 -0
  33. mindroot/coreplugins/admin/static/js/registry-manager-base.js +613 -0
  34. mindroot/coreplugins/admin/static/js/registry-manager-publish-old-delete.js +166 -0
  35. mindroot/coreplugins/admin/static/js/registry-manager.js +351 -0
  36. mindroot/coreplugins/admin/static/js/registry-publish-section.js +377 -0
  37. mindroot/coreplugins/admin/static/js/registry-search-section.js +400 -0
  38. mindroot/coreplugins/admin/static/js/registry-search-section.js.bak +3 -0
  39. mindroot/coreplugins/admin/static/js/registry-settings.js +69 -0
  40. mindroot/coreplugins/admin/static/js/registry-shared-services.js +903 -0
  41. mindroot/coreplugins/admin/static/js/registry-simple-sections.js +85 -0
  42. mindroot/coreplugins/admin/static/js/secure-widget-manager.js +438 -0
  43. mindroot/coreplugins/admin/static/logo.png +0 -0
  44. mindroot/coreplugins/admin/templates/admin.jinja2 +275 -110
  45. mindroot/coreplugins/agent/Assistant/agent.json +27 -11
  46. mindroot/coreplugins/agent/agent.py +2 -2
  47. mindroot/coreplugins/agent/command_parser.py +25 -10
  48. mindroot/coreplugins/agent/templates/system.jinja2 +0 -12
  49. mindroot/coreplugins/chat/__init__.py +4 -1
  50. mindroot/coreplugins/chat/router.py +132 -20
  51. mindroot/coreplugins/chat/router_dedup_patch.py +20 -0
  52. mindroot/coreplugins/chat/services.py +31 -1
  53. mindroot/coreplugins/chat/static/css/action-fix.css +32 -0
  54. mindroot/coreplugins/chat/static/css/admin-custom.css +5 -3
  55. mindroot/coreplugins/chat/static/css/dark.css +24 -3
  56. mindroot/coreplugins/chat/static/css/default.css +24 -3
  57. mindroot/coreplugins/chat/static/css/main.css +1 -0
  58. mindroot/coreplugins/chat/static/js/action.js +137 -60
  59. mindroot/coreplugins/chat/static/js/chat-history.js +3 -0
  60. mindroot/coreplugins/chat/static/js/chat.js +59 -16
  61. mindroot/coreplugins/chat/static/js/chat.js.diff +221 -0
  62. mindroot/coreplugins/chat/static/js/chatform.js +2 -2
  63. mindroot/coreplugins/chat/static/site.webmanifest +1 -1
  64. mindroot/coreplugins/chat/templates/chat.jinja2 +3 -3
  65. mindroot/coreplugins/chat/widget_manager.py +139 -0
  66. mindroot/coreplugins/chat/widget_routes.py +287 -0
  67. mindroot/coreplugins/check_list/inject/admin.jinja2 +1 -1
  68. mindroot/coreplugins/email/__init__.py +2 -0
  69. mindroot/coreplugins/email/email_provider.py +2 -2
  70. mindroot/coreplugins/email/mod.py +100 -0
  71. mindroot/coreplugins/email/services.py +5 -3
  72. mindroot/coreplugins/email/smtp_handler.py +9 -3
  73. mindroot/coreplugins/email/test_email_service.py +75 -0
  74. mindroot/coreplugins/env_manager/mod.py +61 -25
  75. mindroot/coreplugins/home/router.py +37 -2
  76. mindroot/coreplugins/home/static/imgs/logo.png +0 -0
  77. mindroot/coreplugins/home/static/imgs/logo.png.bak +0 -0
  78. mindroot/coreplugins/home/static/imgs/logo_teal.png +0 -0
  79. mindroot/coreplugins/home/static/imgs/logo_teal2.png +0 -0
  80. mindroot/coreplugins/home/static/imgs/logo_teal_detailed.png +0 -0
  81. mindroot/coreplugins/home/static/imgs/logo_teal_python.png +0 -0
  82. mindroot/coreplugins/home/templates/home.jinja2 +15 -6
  83. mindroot/coreplugins/index/indices/default/index.json +39 -6
  84. mindroot/coreplugins/jwt_auth/middleware.py +47 -2
  85. mindroot/coreplugins/jwt_auth/mod.py +40 -17
  86. mindroot/coreplugins/l8n/__init__.py +6 -0
  87. mindroot/coreplugins/l8n/debug_loader.py +85 -0
  88. mindroot/coreplugins/l8n/debug_middleware.py +74 -0
  89. mindroot/coreplugins/l8n/l8n_constants.py +19 -0
  90. mindroot/coreplugins/l8n/language_detection.py +183 -0
  91. mindroot/coreplugins/l8n/middleware.py +151 -0
  92. mindroot/coreplugins/l8n/mod.py +277 -0
  93. mindroot/coreplugins/l8n/monkey_patch_to_delete.py +186 -0
  94. mindroot/coreplugins/l8n/test_enhanced.py +298 -0
  95. mindroot/coreplugins/l8n/test_l8n.py +95 -0
  96. mindroot/coreplugins/l8n/test_l8n_standalone.py +251 -0
  97. mindroot/coreplugins/l8n/test_middleware.py +272 -0
  98. mindroot/coreplugins/l8n/utils.py +232 -0
  99. mindroot/coreplugins/mcp_/__init__.py +14 -0
  100. mindroot/coreplugins/mcp_/catalog_commands.py +328 -0
  101. mindroot/coreplugins/mcp_/catalog_manager.py +263 -0
  102. mindroot/coreplugins/mcp_/dynamic_commands.py +154 -0
  103. mindroot/coreplugins/mcp_/mcp_manager.py +1031 -0
  104. mindroot/coreplugins/mcp_/mod.py +367 -0
  105. mindroot/coreplugins/mcp_/oauth_storage.py +144 -0
  106. mindroot/coreplugins/mcp_/server_installer.py +79 -0
  107. mindroot/coreplugins/mcp_/setup.py +26 -0
  108. mindroot/coreplugins/mcp_/test_dynamic_commands.py +134 -0
  109. mindroot/coreplugins/mcp_/testmcpclient.py +92 -0
  110. mindroot/coreplugins/persona/mod.py +12 -7
  111. mindroot/coreplugins/signup/templates/signup.jinja2 +1 -1
  112. mindroot/coreplugins/subscriptions/__init__.py +1 -0
  113. mindroot/coreplugins/subscriptions/mod.py +14 -3
  114. mindroot/coreplugins/subscriptions/router.py +3 -0
  115. mindroot/coreplugins/user_service/__init__.py +1 -2
  116. mindroot/coreplugins/user_service/admin_init.py +1 -0
  117. mindroot/coreplugins/user_service/email_service.py +72 -17
  118. mindroot/coreplugins/user_service/mod.py +10 -2
  119. mindroot/coreplugins/user_service/router.py +2 -0
  120. mindroot/lib/auth/api_key.py +28 -0
  121. mindroot/lib/cli/plugins.py +94 -0
  122. mindroot/lib/plugins/default_plugin_manifest.json +20 -0
  123. mindroot/lib/plugins/installation.py +5 -5
  124. mindroot/lib/plugins/l8n_static_handler.py +225 -0
  125. mindroot/lib/plugins/loader.py +33 -3
  126. mindroot/lib/plugins/loader_with_l8n.py +281 -0
  127. mindroot/lib/plugins/manifest.py +236 -24
  128. mindroot/lib/providers/commands.py +3 -1
  129. mindroot/lib/route_decorators.py +5 -5
  130. mindroot/lib/templates.py +183 -11
  131. mindroot/lib/utils/merge_arrays.py +1 -1
  132. mindroot/migrate.py +39 -20
  133. mindroot/registry/data_access.py +1 -1
  134. mindroot/server.py +42 -13
  135. mindroot/server_missing_normal_args.py +197 -0
  136. mindroot/server_prev.py +173 -0
  137. {mindroot-9.3.0.dist-info → mindroot-9.6.0.dist-info}/METADATA +7 -2
  138. {mindroot-9.3.0.dist-info → mindroot-9.6.0.dist-info}/RECORD +143 -113
  139. mindroot/coreplugins/admin/plugin_manager_backup.py +0 -615
  140. mindroot/coreplugins/admin/static/favicon/about.txt +0 -6
  141. mindroot/coreplugins/admin/static/favicon/android-chrome-512x512.png +0 -0
  142. mindroot/coreplugins/admin/static/favicon/apple-touch-icon.png +0 -0
  143. mindroot/coreplugins/admin/static/favicon/favicon-16x16.png +0 -0
  144. mindroot/coreplugins/admin/static/favicon/favicon-32x32.png +0 -0
  145. mindroot/coreplugins/admin/static/favicon/favicon.ico +0 -0
  146. mindroot/coreplugins/admin/static/favicon/favicon_io (1)/about.txt +0 -6
  147. mindroot/coreplugins/admin/static/favicon/favicon_io (1)/android-chrome-192x192.png +0 -0
  148. mindroot/coreplugins/admin/static/favicon/favicon_io (1)/android-chrome-512x512.png +0 -0
  149. mindroot/coreplugins/admin/static/favicon/favicon_io (1)/apple-touch-icon.png +0 -0
  150. mindroot/coreplugins/admin/static/favicon/favicon_io (1)/favicon-16x16.png +0 -0
  151. mindroot/coreplugins/admin/static/favicon/favicon_io (1)/favicon-32x32.png +0 -0
  152. mindroot/coreplugins/admin/static/favicon/favicon_io (1)/favicon.ico +0 -0
  153. mindroot/coreplugins/admin/static/favicon/favicon_io (1)/site.webmanifest +0 -1
  154. mindroot/coreplugins/admin/static/favicon/logo.png +0 -0
  155. mindroot/coreplugins/admin/static/favicon/site.webmanifest +0 -1
  156. mindroot/coreplugins/admin/static/js/backup/agent-editor.js +0 -186
  157. mindroot/coreplugins/admin/static/js/backup/agent-form.js +0 -1133
  158. mindroot/coreplugins/admin/static/js/backup/agent-list.js +0 -94
  159. mindroot/coreplugins/chat/static/favicon/about.txt +0 -6
  160. mindroot/coreplugins/chat/static/favicon/android-chrome-192x192.png +0 -0
  161. mindroot/coreplugins/chat/static/favicon/android-chrome-512x512.png +0 -0
  162. mindroot/coreplugins/chat/static/favicon/apple-touch-icon.png +0 -0
  163. mindroot/coreplugins/chat/static/favicon/favicon-16x16.png +0 -0
  164. mindroot/coreplugins/chat/static/favicon/favicon-32x32.png +0 -0
  165. mindroot/coreplugins/chat/static/favicon/favicon.ico +0 -0
  166. mindroot/coreplugins/chat/static/favicon/favicon_io (1)/about.txt +0 -6
  167. mindroot/coreplugins/chat/static/favicon/favicon_io (1)/android-chrome-192x192.png +0 -0
  168. mindroot/coreplugins/chat/static/favicon/favicon_io (1)/android-chrome-512x512.png +0 -0
  169. mindroot/coreplugins/chat/static/favicon/favicon_io (1)/apple-touch-icon.png +0 -0
  170. mindroot/coreplugins/chat/static/favicon/favicon_io (1)/favicon-16x16.png +0 -0
  171. mindroot/coreplugins/chat/static/favicon/favicon_io (1)/favicon-32x32.png +0 -0
  172. mindroot/coreplugins/chat/static/favicon/favicon_io (1)/favicon.ico +0 -0
  173. mindroot/coreplugins/chat/static/favicon/favicon_io (1)/site.webmanifest +0 -1
  174. mindroot/coreplugins/chat/static/favicon/logo.png +0 -0
  175. mindroot/coreplugins/chat/static/favicon/site.webmanifest +0 -1
  176. mindroot/coreplugins/index/default.json +0 -76
  177. mindroot/coreplugins/user_service/file_trigger_service.py +0 -12
  178. mindroot/coreplugins/user_service/hooks.py +0 -23
  179. /mindroot/coreplugins/{admin/static/favicon/android-chrome-192x192.png → home/static/imgs/backuplogo.png} +0 -0
  180. {mindroot-9.3.0.dist-info → mindroot-9.6.0.dist-info}/WHEEL +0 -0
  181. {mindroot-9.3.0.dist-info → mindroot-9.6.0.dist-info}/entry_points.txt +0 -0
  182. {mindroot-9.3.0.dist-info → mindroot-9.6.0.dist-info}/licenses/LICENSE +0 -0
  183. {mindroot-9.3.0.dist-info → mindroot-9.6.0.dist-info}/top_level.txt +0 -0
@@ -12,6 +12,7 @@ from lib.plugins import (
12
12
  from lib.plugins.installation import download_github_files
13
13
  from lib.streamcmd import stream_command_as_events
14
14
  import asyncio
15
+ import httpx
15
16
 
16
17
 
17
18
  router = APIRouter()
@@ -52,15 +53,22 @@ import sys, os, shlex
52
53
  async def stream_install_plugin(request: StreamInstallRequest):
53
54
  """Stream the installation process of a plugin using SSE (POST method)."""
54
55
  # Prepare the command based on the source
55
- if request.source == 'github_direct':
56
- cmd = [sys.executable, '-m', 'pip', 'install', '-e', request.source_path, '-v', '--no-cache-dir']
56
+ print("Stream Install Request:", request)
57
+ if request.source == 'github_direct' or request.source == 'github':
58
+ if request.source_path.startswith('https://'):
59
+ # For direct GitHub URLs, we can use the pip install directly
60
+ cmd = [sys.executable, '-m', 'pip', 'install', request.source_path, '-v', '--no-cache-dir']
61
+ else:
62
+ cmd = [sys.executable, '-m', 'pip', 'install', '-e', request.source_path, '-v', '--no-cache-dir']
57
63
  elif request.source == 'local':
58
64
  cmd = [sys.executable, '-m', 'pip', 'install', '-e', request.source_path, '-v', '--no-cache-dir']
59
65
  elif request.source == 'pypi':
60
66
  cmd = [sys.executable, '-m', 'pip', 'install', request.plugin, '-v', '--no-cache-dir']
61
67
  else:
68
+ print("Invalid source")
62
69
  return {"success": False, "message": "Invalid source"}
63
70
 
71
+ print("Command to execute:", cmd)
64
72
  # For GitHub installations, use the plugin_install function which handles the download and extraction
65
73
  if request.source == 'github':
66
74
  try:
@@ -69,10 +77,14 @@ async def stream_install_plugin(request: StreamInstallRequest):
69
77
  repo_path = parts[0]
70
78
  tag = parts[1] if len(parts) > 1 else None
71
79
 
80
+ print(1)
81
+
72
82
  async def stream_github_install():
83
+ print("dling")
73
84
  yield {"event": "message", "data": f"Downloading GitHub repository {repo_path}..."}
74
85
 
75
86
  try:
87
+ print("dling 2")
76
88
  plugin_dir, _, plugin_info = download_github_files(repo_path, tag)
77
89
 
78
90
  cmd = [sys.executable, '-m', 'pip', 'install', '-e', plugin_dir, '-v', '--no-cache-dir']
@@ -85,18 +97,24 @@ async def stream_install_plugin(request: StreamInstallRequest):
85
97
  metadata=plugin_info
86
98
  )
87
99
  except Exception as e:
100
+ print(e)
88
101
  yield {"event": "error", "data": f"Error installing from GitHub: {str(e)}"}
89
102
 
90
103
  return EventSourceResponse(stream_github_install())
91
104
  except Exception as e:
105
+ print(3)
106
+ print(e)
92
107
  return {"success": False, "message": f"Error setting up GitHub installation: {str(e)}"}
93
108
 
94
109
  # For other sources, use our streamcmd module to stream the command output
110
+ print("stream cmd as events")
95
111
  return EventSourceResponse(stream_command_as_events(cmd))
112
+
96
113
  @router.get("/stream-install-plugin", response_class=EventSourceResponse)
97
114
  async def stream_install_plugin_get(request: Request):
98
115
  """Stream the installation process of a plugin using SSE (GET method)."""
99
116
  # Extract parameters from query string
117
+ print("Stream Install GET Request:", request.query_params)
100
118
  plugin = request.query_params.get("plugin", "")
101
119
  source = request.query_params.get("source", "")
102
120
  source_path = request.query_params.get("source_path", "")
@@ -114,22 +132,36 @@ async def stream_install_plugin_get(request: Request):
114
132
  else:
115
133
  return {"success": False, "message": "Invalid source"}
116
134
 
135
+ print("Command to execute:", cmd)
136
+ tag = None
117
137
  # For GitHub installations, use the plugin_install function which handles the download and extraction
118
138
  if source == 'github':
119
139
  try:
120
140
  # Use the streaming approach for GitHub installations
141
+ print("source_path:", source_path)
121
142
  parts = source_path.split(':')
122
143
  repo_path = parts[0]
123
144
  tag = parts[1] if len(parts) > 1 else None
124
-
145
+ print("repo_path:", repo_path, "tag:", tag)
125
146
  # First yield a message about downloading
147
+ #
126
148
  async def stream_github_install():
127
149
  yield {"event": "message", "data": f"Downloading GitHub repository {repo_path}..."}
128
-
150
+ repo_path_ = repo_path
151
+ tag_ = tag
129
152
  # Download and extract the GitHub repository
130
153
  try:
131
- plugin_dir, _, plugin_info = download_github_files(repo_path, tag)
132
-
154
+ if source_path.startswith('https://'):
155
+ print("Processing direct GitHub URL")
156
+ repo_path_ = source_path
157
+ tag_ = None
158
+ parts = repo_path_.split('/')
159
+ if len(parts) >= 5:
160
+ repo_path_ = f"{parts[3]}/{parts[4]}"
161
+
162
+ print("repo_path_:", repo_path_)
163
+ plugin_dir, _, plugin_info = download_github_files(repo_path_, tag_)
164
+ print('ok')
133
165
  # Now stream the installation from the local directory
134
166
  cmd = [sys.executable, '-m', 'pip', 'install', '-e', plugin_dir, '-v', '--no-cache-dir']
135
167
  async for event in stream_command_as_events(cmd):
@@ -138,15 +170,17 @@ async def stream_install_plugin_get(request: Request):
138
170
  # Update the plugin manifest
139
171
  update_plugin_manifest(
140
172
  plugin_info['name'], 'github', os.path.abspath(plugin_dir),
141
- remote_source=repo_path, version=plugin_info.get('version', '0.0.1'),
173
+ remote_source=repo_path_, version=plugin_info.get('version', '0.0.1'),
142
174
  metadata=plugin_info
143
175
  )
144
176
  except Exception as e:
145
- yield {"event": "error", "data": f"Error installing from GitHub: {str(e)}"}
177
+ trace = traceback.format_exc()
178
+ yield {"event": "error", "data": f"Error installing from GitHub: {str(e)} \n\n{trace}"}
146
179
 
147
180
  return EventSourceResponse(stream_github_install())
148
181
  except Exception as e:
149
- return {"success": False, "message": f"Error installing from GitHub: {str(e)}"}
182
+ trace = traceback.format_exc()
183
+ return {"success": False, "message": f"Error installing from GitHub: {str(e)}\n\n{trace}"}
150
184
 
151
185
  # Use our new streamcmd module
152
186
  return EventSourceResponse(stream_command_as_events(cmd))
@@ -360,3 +394,65 @@ def discover_plugins(directory):
360
394
  continue
361
395
 
362
396
  return discovered
397
+
398
+ async def publish_plugin_from_github(repo: str, registry_token: str, registry_url: str):
399
+ """
400
+ Fetches plugin_info.json from a GitHub repo and publishes it to the registry.
401
+ """
402
+ plugin_info = None
403
+ # Try to fetch from 'main' and then 'master' branch
404
+ for branch in ['main', 'master']:
405
+ url = f"https://raw.githubusercontent.com/{repo}/{branch}/plugin_info.json"
406
+ async with httpx.AsyncClient() as client:
407
+ try:
408
+ response = await client.get(url)
409
+ if response.status_code == 200:
410
+ plugin_info = response.json()
411
+ break
412
+ except httpx.RequestError as e:
413
+ # This might happen if the repo is private or other network issues
414
+ print(f"Error fetching from {url}: {e}")
415
+ continue
416
+
417
+ if not plugin_info:
418
+ raise Exception(f"Could not find or access plugin_info.json in repo {repo} on 'main' or 'master' branch.")
419
+
420
+ # Construct the payload for the registry's /publish endpoint
421
+ publish_data = {
422
+ "title": plugin_info.get("name"),
423
+ "description": plugin_info.get("description", ""),
424
+ "category": "plugin",
425
+ "content_type": "mindroot_plugin",
426
+ "version": plugin_info.get("version", "0.1.0"),
427
+ "github_url": f"https://github.com/{repo}",
428
+ "pypi_module": plugin_info.get("pypi_module"),
429
+ "commands": plugin_info.get("commands", []),
430
+ "services": plugin_info.get("services", []),
431
+ "tags": plugin_info.get("tags", ["plugin"]),
432
+ "dependencies": plugin_info.get("dependencies", []),
433
+ "data": {
434
+ "plugin_info": plugin_info,
435
+ "installation": {
436
+ "type": "github",
437
+ "source_path": repo
438
+ }
439
+ }
440
+ }
441
+
442
+ # Post the data to the registry
443
+ publish_url = f"{registry_url}/publish"
444
+ headers = {
445
+ "Authorization": f"Bearer {registry_token}",
446
+ "Content-Type": "application/json"
447
+ }
448
+ async with httpx.AsyncClient() as client:
449
+ response = await client.post(publish_url, json=publish_data, headers=headers)
450
+
451
+ if response.status_code >= 400:
452
+ try:
453
+ error_detail = response.json().get("detail", response.text)
454
+ except:
455
+ error_detail = response.text
456
+ raise Exception(f"Failed to publish to registry: {response.status_code} - {error_detail}")
457
+
458
+ return response.json()
@@ -0,0 +1,23 @@
1
+ from fastapi import APIRouter
2
+ from lib.route_decorators import requires_role
3
+
4
+ # Import the separate route modules
5
+ from .plugin_routes import router as plugin_routes
6
+ from .mcp_routes import router as mcp_routes
7
+ from .mcp_catalog_routes import router as mcp_catalog_routes
8
+ from .registry_settings_routes import router as registry_settings_routes
9
+ from .mcp_publish_routes import router as mcp_publish_routes
10
+ from .mcp_registry_routes import router as mcp_registry_routes
11
+
12
+ # Create main router with admin role requirement
13
+ router = APIRouter(
14
+ dependencies=[requires_role('admin')]
15
+ )
16
+
17
+ # Include all the sub-routers
18
+ router.include_router(plugin_routes, tags=["plugins"])
19
+ router.include_router(mcp_routes, tags=["mcp"])
20
+ router.include_router(mcp_catalog_routes, tags=["mcp-catalog"])
21
+ router.include_router(registry_settings_routes, tags=["registry-settings"])
22
+ router.include_router(mcp_publish_routes, tags=["mcp-publish"])
23
+ router.include_router(mcp_registry_routes, tags=["mcp-registry"])
@@ -0,0 +1,145 @@
1
+ from fastapi import APIRouter, HTTPException, Depends, Header
2
+ from pydantic import BaseModel
3
+ import json
4
+ from typing import Optional, List
5
+ from lib import plugins
6
+ from . import plugin_manager
7
+ from lib.auth.cognito import get_current_user
8
+ from lib.config import get_settings
9
+
10
+ # Import MCP components
11
+ try:
12
+ from mindroot.coreplugins.mcp.enhanced_mod import enhanced_mcp_manager
13
+ from mindroot.coreplugins.mcp.mod import MCPServer
14
+ except ImportError:
15
+ # Mock objects if MCP plugin is not fully installed, to prevent startup crash
16
+ enhanced_mcp_manager = None
17
+ MCPServer = None
18
+
19
+ router = APIRouter()
20
+
21
+ class PluginUpdateRequest(BaseModel):
22
+ plugins: dict
23
+
24
+ @router.post("/update-plugins")
25
+ def update_plugins(request: PluginUpdateRequest):
26
+ try:
27
+ with open('plugins.json', 'r') as file:
28
+ plugins_data = json.load(file)
29
+
30
+ for plugin in plugins_data:
31
+ if plugin['name'] in request.plugins:
32
+ plugin['enabled'] = request.plugins[plugin['name']]
33
+
34
+ with open('plugins.json', 'w') as file:
35
+ json.dump(plugins_data, file, indent=2)
36
+
37
+ plugins.load('data/plugin_manifest.json')
38
+
39
+ return {"message": "Plugins updated successfully"}
40
+ except Exception as e:
41
+ raise HTTPException(status_code=500, detail=str(e))
42
+
43
+ @router.get("/get-plugins")
44
+ async def get_plugins():
45
+ try:
46
+ return plugins.load_plugin_manifest()
47
+ except Exception as e:
48
+ raise HTTPException(status_code=500, detail=str(e))
49
+
50
+ class GithubPublishRequest(BaseModel):
51
+ repo: str
52
+ registry_url: str
53
+
54
+ @router.post("/plugins/publish_from_github")
55
+ async def publish_from_github(request: GithubPublishRequest, authorization: Optional[str] = Header(None), user: dict = Depends(get_current_user)):
56
+ if not user:
57
+ raise HTTPException(status_code=401, detail="Not authorized for Mindroot Admin")
58
+
59
+ registry_token = None
60
+ if authorization and authorization.startswith("Bearer "):
61
+ registry_token = authorization.split(" ")[1]
62
+
63
+ if not registry_token:
64
+ raise HTTPException(status_code=401, detail="Registry auth token not provided")
65
+
66
+ try:
67
+ result = await plugin_manager.publish_plugin_from_github(request.repo, registry_token, request.registry_url)
68
+ return {"message": f"Plugin '{result.get('title')}' published successfully!", "data": result}
69
+ except Exception as e:
70
+ import traceback
71
+ traceback.print_exc()
72
+ raise HTTPException(status_code=500, detail=str(e))
73
+
74
+ class GithubPublishRequest(BaseModel):
75
+ repo: str
76
+
77
+ @router.post("/plugins/publish_from_github")
78
+ async def publish_from_github(request: GithubPublishRequest, user: dict = Depends(get_current_user)):
79
+ if not user:
80
+ raise HTTPException(status_code=401, detail="Not authorized")
81
+ try:
82
+ result = await plugin_manager.publish_plugin_from_github(request.repo, user)
83
+ return {"message": "Plugin published successfully", "data": result}
84
+ except Exception as e:
85
+ raise HTTPException(status_code=500, detail=str(e))
86
+
87
+ # --- MCP Integration Routes ---
88
+
89
+ class McpServerRequest(BaseModel):
90
+ server_name: str
91
+
92
+ @router.get("/mcp/list")
93
+ async def list_mcp_servers(user: dict = Depends(get_current_user)):
94
+ if not enhanced_mcp_manager:
95
+ raise HTTPException(status_code=501, detail="MCP Plugin not available")
96
+ servers = [s.dict() for s in enhanced_mcp_manager.servers.values()]
97
+ return {"success": True, "data": servers}
98
+
99
+ @router.post("/mcp/add")
100
+ async def add_mcp_server(server_config: MCPServer, user: dict = Depends(get_current_user)):
101
+ if not enhanced_mcp_manager:
102
+ raise HTTPException(status_code=501, detail="MCP Plugin not available")
103
+ try:
104
+ # The server_config is a full MCPServer object from the registry
105
+ enhanced_mcp_manager.add_server(server_config.name, server_config)
106
+ return {"success": True, "message": f"Server '{server_config.name}' added."}
107
+ except Exception as e:
108
+ raise HTTPException(status_code=500, detail=str(e))
109
+
110
+ @router.post("/mcp/remove")
111
+ async def remove_mcp_server(request: McpServerRequest, user: dict = Depends(get_current_user)):
112
+ if not enhanced_mcp_manager:
113
+ raise HTTPException(status_code=501, detail="MCP Plugin not available")
114
+ try:
115
+ await enhanced_mcp_manager.remove_server(request.server_name)
116
+ return {"success": True, "message": f"Server '{request.server_name}' removed."}
117
+ except Exception as e:
118
+ raise HTTPException(status_code=500, detail=str(e))
119
+
120
+ @router.post("/mcp/connect")
121
+ async def connect_mcp_server(request: McpServerRequest, user: dict = Depends(get_current_user)):
122
+ if not enhanced_mcp_manager:
123
+ raise HTTPException(status_code=501, detail="MCP Plugin not available")
124
+ try:
125
+ success = await enhanced_mcp_manager.connect_server(request.server_name)
126
+ if success:
127
+ return {"success": True, "message": f"Server '{request.server_name}' connected."}
128
+ else:
129
+ raise HTTPException(status_code=500, detail=f"Failed to connect to server '{request.server_name}'. Check logs.")
130
+ except Exception as e:
131
+ raise HTTPException(status_code=500, detail=str(e))
132
+
133
+ @router.post("/mcp/disconnect")
134
+ async def disconnect_mcp_server(request: McpServerRequest, user: dict = Depends(get_current_user)):
135
+ if not enhanced_mcp_manager:
136
+ raise HTTPException(status_code=501, detail="MCP Plugin not available")
137
+ try:
138
+ success = await enhanced_mcp_manager.disconnect_server(request.server_name)
139
+ if success:
140
+ return {"success": True, "message": f"Server '{request.server_name}' disconnected."}
141
+ else:
142
+ raise HTTPException(status_code=500, detail=f"Failed to disconnect from server '{request.server_name}'.")
143
+ except Exception as e:
144
+ raise HTTPException(status_code=500, detail=str(e))
145
+
@@ -0,0 +1,114 @@
1
+ from fastapi import APIRouter, HTTPException, Header
2
+ from pydantic import BaseModel
3
+ import json
4
+ import os
5
+ from typing import Optional
6
+ from lib import plugins
7
+ from . import plugin_manager
8
+ from lib.route_decorators import requires_role
9
+
10
+ # Create router with admin role requirement
11
+ router = APIRouter(
12
+ dependencies=[requires_role('admin')]
13
+ )
14
+
15
+ class PluginUpdateRequest(BaseModel):
16
+ plugins: dict
17
+
18
+ class GithubPublishRequest(BaseModel):
19
+ repo: str
20
+ registry_url: Optional[str] = None
21
+
22
+ # --- Plugin Management Routes ---
23
+
24
+ @router.post("/update-plugins")
25
+ def update_plugins(request: PluginUpdateRequest):
26
+ """Update plugin enabled/disabled status."""
27
+ try:
28
+ with open('plugins.json', 'r') as file:
29
+ plugins_data = json.load(file)
30
+
31
+ for plugin in plugins_data:
32
+ if plugin['name'] in request.plugins:
33
+ plugin['enabled'] = request.plugins[plugin['name']]
34
+
35
+ with open('plugins.json', 'w') as file:
36
+ json.dump(plugins_data, file, indent=2)
37
+
38
+ plugins.load('data/plugin_manifest.json')
39
+
40
+ return {"message": "Plugins updated successfully"}
41
+ except Exception as e:
42
+ raise HTTPException(status_code=500, detail=str(e))
43
+
44
+ @router.get("/get-plugins")
45
+ async def get_plugins():
46
+ """Get list of all plugins."""
47
+ try:
48
+ return plugins.load_plugin_manifest()
49
+ except Exception as e:
50
+ raise HTTPException(status_code=500, detail=str(e))
51
+
52
+ @router.post("/plugins/publish_from_github")
53
+ async def publish_plugin_from_github(
54
+ request: GithubPublishRequest,
55
+ authorization: Optional[str] = Header(None)
56
+ ):
57
+ """Publish a plugin from GitHub repository to the registry.
58
+
59
+ This endpoint allows publishing a plugin by simply providing the GitHub
60
+ repository in the format 'username/repo'. It will fetch the plugin_info.json
61
+ from the repository and publish it to the configured registry.
62
+
63
+ The registry token can be provided via:
64
+ 1. Authorization header: "Bearer <token>"
65
+ 2. REGISTRY_TOKEN environment variable
66
+ 3. registry_token in data/registry_settings.json
67
+ """
68
+ try:
69
+ # Get registry token from multiple sources
70
+ registry_token = None
71
+
72
+ # 1. Try Authorization header
73
+ if authorization and authorization.startswith("Bearer "):
74
+ registry_token = authorization.split(" ")[1]
75
+
76
+ # 2. Try environment variable
77
+ if not registry_token:
78
+ registry_token = os.getenv('REGISTRY_TOKEN')
79
+
80
+ # 3. Try settings file
81
+ if not registry_token:
82
+ try:
83
+ settings_file = 'data/registry_settings.json'
84
+ if os.path.exists(settings_file):
85
+ with open(settings_file, 'r') as f:
86
+ settings = json.load(f)
87
+ registry_token = settings.get('registry_token')
88
+ except Exception as e:
89
+ print(f"Error reading registry settings: {e}")
90
+
91
+ if not registry_token:
92
+ raise HTTPException(
93
+ status_code=401,
94
+ detail="Registry authentication token not provided. Please provide via Authorization header, REGISTRY_TOKEN environment variable, or registry_settings.json file."
95
+ )
96
+
97
+ # Use the existing plugin_manager functionality
98
+ registry_url = request.registry_url or "https://registry.mindroot.io"
99
+
100
+ result = await plugin_manager.publish_plugin_from_github(
101
+ request.repo,
102
+ registry_token,
103
+ registry_url
104
+ )
105
+
106
+ return {
107
+ "success": True,
108
+ "message": f"Plugin '{result.get('title', request.repo)}' published successfully!",
109
+ "data": result
110
+ }
111
+ except Exception as e:
112
+ import traceback
113
+ traceback.print_exc()
114
+ raise HTTPException(status_code=500, detail=str(e))
@@ -0,0 +1,140 @@
1
+ from fastapi import APIRouter, HTTPException
2
+ import json
3
+ import os
4
+ from lib.route_decorators import requires_role
5
+
6
+ # Create router with admin role requirement
7
+ router = APIRouter(
8
+ dependencies=[requires_role('admin')]
9
+ )
10
+
11
+ # --- Registry Settings Routes ---
12
+
13
+ @router.get("/registry/settings")
14
+ async def get_registry_settings():
15
+ """Get registry settings including token status."""
16
+ try:
17
+ settings_file = 'data/registry_settings.json'
18
+ settings = {}
19
+
20
+ if os.path.exists(settings_file):
21
+ with open(settings_file, 'r') as f:
22
+ settings = json.load(f)
23
+
24
+ # Don't return the actual token, just indicate if it's set
25
+ return {
26
+ "success": True,
27
+ "data": {
28
+ "registry_url": settings.get("registry_url", "https://registry.mindroot.io"),
29
+ "has_token": bool(settings.get("registry_token")),
30
+ "token_source": "file" if settings.get("registry_token") else "env" if os.getenv('REGISTRY_TOKEN') else "none"
31
+ }
32
+ }
33
+ except Exception as e:
34
+ raise HTTPException(status_code=500, detail=str(e))
35
+
36
+ @router.post("/registry/settings")
37
+ async def update_registry_settings(settings_data: dict):
38
+ """Update registry settings."""
39
+ try:
40
+ settings_file = 'data/registry_settings.json'
41
+
42
+ # Ensure data directory exists
43
+ os.makedirs('data', exist_ok=True)
44
+
45
+ # Load existing settings
46
+ settings = {}
47
+ if os.path.exists(settings_file):
48
+ with open(settings_file, 'r') as f:
49
+ settings = json.load(f)
50
+
51
+ # Update with new data
52
+ settings.update(settings_data)
53
+
54
+ # Save updated settings
55
+ with open(settings_file, 'w') as f:
56
+ json.dump(settings, f, indent=2)
57
+
58
+ return {
59
+ "success": True,
60
+ "message": "Registry settings updated successfully.",
61
+ "data": {
62
+ "registry_url": settings.get("registry_url", "https://registry.mindroot.io"),
63
+ "has_token": bool(settings.get("registry_token")),
64
+ "token_source": "file" if settings.get("registry_token") else "env" if os.getenv('REGISTRY_TOKEN') else "none"
65
+ }
66
+ }
67
+ except Exception as e:
68
+ raise HTTPException(status_code=500, detail=str(e))
69
+
70
+ @router.delete("/registry/settings/token")
71
+ async def clear_registry_token():
72
+ """Clear the stored registry token."""
73
+ try:
74
+ settings_file = 'data/registry_settings.json'
75
+
76
+ if os.path.exists(settings_file):
77
+ with open(settings_file, 'r') as f:
78
+ settings = json.load(f)
79
+
80
+ # Remove token if it exists
81
+ if 'registry_token' in settings:
82
+ del settings['registry_token']
83
+
84
+ with open(settings_file, 'w') as f:
85
+ json.dump(settings, f, indent=2)
86
+
87
+ return {
88
+ "success": True,
89
+ "message": "Registry token cleared successfully."
90
+ }
91
+ except Exception as e:
92
+ raise HTTPException(status_code=500, detail=str(e))
93
+
94
+ @router.post("/registry/test-connection")
95
+ async def test_registry_connection():
96
+ """Test connection to the registry."""
97
+ try:
98
+ import httpx
99
+
100
+ settings_file = 'data/registry_settings.json'
101
+ registry_url = "https://registry.mindroot.io"
102
+
103
+ if os.path.exists(settings_file):
104
+ with open(settings_file, 'r') as f:
105
+ settings = json.load(f)
106
+ registry_url = settings.get("registry_url", registry_url)
107
+
108
+ # Test connection to registry
109
+ async with httpx.AsyncClient(timeout=10.0) as client:
110
+ response = await client.get(f"{registry_url}/stats")
111
+
112
+ if response.status_code == 200:
113
+ stats = response.json()
114
+ return {
115
+ "success": True,
116
+ "message": "Successfully connected to registry.",
117
+ "data": {
118
+ "registry_url": registry_url,
119
+ "stats": stats
120
+ }
121
+ }
122
+ else:
123
+ return {
124
+ "success": False,
125
+ "message": f"Registry returned status code {response.status_code}",
126
+ "data": {
127
+ "registry_url": registry_url,
128
+ "status_code": response.status_code
129
+ }
130
+ }
131
+
132
+ except Exception as e:
133
+ return {
134
+ "success": False,
135
+ "message": f"Failed to connect to registry: {str(e)}",
136
+ "data": {
137
+ "registry_url": registry_url,
138
+ "error": str(e)
139
+ }
140
+ }