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
@@ -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))