mindroot 9.2.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.
Files changed (186) 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 +63 -0
  14. mindroot/coreplugins/admin/plugin_router.py +1 -1
  15. mindroot/coreplugins/admin/plugin_router_fixed.py +23 -0
  16. mindroot/coreplugins/admin/plugin_router_new_not_working.py +145 -0
  17. mindroot/coreplugins/admin/plugin_routes.py +114 -0
  18. mindroot/coreplugins/admin/registry_settings_routes.py +140 -0
  19. mindroot/coreplugins/admin/router.py +116 -15
  20. mindroot/coreplugins/admin/service_models.py +1 -1
  21. mindroot/coreplugins/admin/settings_router.py +1 -0
  22. mindroot/coreplugins/admin/static/css/admin-custom.css +357 -2
  23. mindroot/coreplugins/admin/static/css/dark.css +1 -0
  24. mindroot/coreplugins/admin/static/css/default.css +4 -0
  25. mindroot/coreplugins/admin/static/js/about-info.js +367 -0
  26. mindroot/coreplugins/admin/static/js/agent-form.js +83 -3
  27. mindroot/coreplugins/admin/static/js/api-key-script.js +307 -0
  28. mindroot/coreplugins/admin/static/js/mcp-manager.js +348 -0
  29. mindroot/coreplugins/admin/static/js/mcp-publisher.js +780 -0
  30. mindroot/coreplugins/admin/static/js/persona-editor.js +34 -5
  31. mindroot/coreplugins/admin/static/js/plugin-toggle.js +1 -1
  32. mindroot/coreplugins/admin/static/js/recommended-plugin-install.js +63 -0
  33. mindroot/coreplugins/admin/static/js/registry-auth-section.js +132 -0
  34. mindroot/coreplugins/admin/static/js/registry-manager-base.js +613 -0
  35. mindroot/coreplugins/admin/static/js/registry-manager-old.js +385 -0
  36. mindroot/coreplugins/admin/static/js/registry-manager-publish-old-delete.js +166 -0
  37. mindroot/coreplugins/admin/static/js/registry-manager.js +351 -0
  38. mindroot/coreplugins/admin/static/js/registry-publish-section.js +377 -0
  39. mindroot/coreplugins/admin/static/js/registry-search-section.js +400 -0
  40. mindroot/coreplugins/admin/static/js/registry-search-section.js.bak +3 -0
  41. mindroot/coreplugins/admin/static/js/registry-settings.js +69 -0
  42. mindroot/coreplugins/admin/static/js/registry-shared-services.js +857 -0
  43. mindroot/coreplugins/admin/static/js/registry-simple-sections.js +85 -0
  44. mindroot/coreplugins/admin/static/js/secure-widget-manager.js +438 -0
  45. mindroot/coreplugins/admin/static/logo.png +0 -0
  46. mindroot/coreplugins/admin/templates/admin.jinja2 +275 -110
  47. mindroot/coreplugins/agent/Assistant/agent.json +27 -11
  48. mindroot/coreplugins/agent/agent.py +2 -2
  49. mindroot/coreplugins/agent/command_parser.py +25 -10
  50. mindroot/coreplugins/agent/templates/system.jinja2 +0 -12
  51. mindroot/coreplugins/chat/__init__.py +4 -1
  52. mindroot/coreplugins/chat/router.py +132 -20
  53. mindroot/coreplugins/chat/router_dedup_patch.py +20 -0
  54. mindroot/coreplugins/chat/services.py +31 -1
  55. mindroot/coreplugins/chat/static/css/action-fix.css +32 -0
  56. mindroot/coreplugins/chat/static/css/admin-custom.css +5 -3
  57. mindroot/coreplugins/chat/static/css/dark.css +24 -3
  58. mindroot/coreplugins/chat/static/css/default.css +24 -3
  59. mindroot/coreplugins/chat/static/css/main.css +1 -0
  60. mindroot/coreplugins/chat/static/js/action.js +137 -60
  61. mindroot/coreplugins/chat/static/js/chat-history.js +3 -0
  62. mindroot/coreplugins/chat/static/js/chat.js +59 -16
  63. mindroot/coreplugins/chat/static/js/chat.js.diff +221 -0
  64. mindroot/coreplugins/chat/static/js/chatform.js +2 -2
  65. mindroot/coreplugins/chat/static/site.webmanifest +1 -1
  66. mindroot/coreplugins/chat/templates/chat.jinja2 +3 -3
  67. mindroot/coreplugins/chat/widget_manager.py +139 -0
  68. mindroot/coreplugins/chat/widget_routes.py +287 -0
  69. mindroot/coreplugins/check_list/inject/admin.jinja2 +1 -1
  70. mindroot/coreplugins/email/__init__.py +2 -0
  71. mindroot/coreplugins/email/email_provider.py +2 -2
  72. mindroot/coreplugins/email/mod.py +100 -0
  73. mindroot/coreplugins/email/services.py +5 -3
  74. mindroot/coreplugins/email/smtp_handler.py +9 -3
  75. mindroot/coreplugins/email/test_email_service.py +75 -0
  76. mindroot/coreplugins/env_manager/mod.py +61 -25
  77. mindroot/coreplugins/home/router.py +37 -2
  78. mindroot/coreplugins/home/static/imgs/logo.png +0 -0
  79. mindroot/coreplugins/home/static/imgs/logo.png.bak +0 -0
  80. mindroot/coreplugins/home/static/imgs/logo_teal.png +0 -0
  81. mindroot/coreplugins/home/static/imgs/logo_teal2.png +0 -0
  82. mindroot/coreplugins/home/static/imgs/logo_teal_detailed.png +0 -0
  83. mindroot/coreplugins/home/static/imgs/logo_teal_python.png +0 -0
  84. mindroot/coreplugins/home/templates/home.jinja2 +15 -6
  85. mindroot/coreplugins/index/handlers/plugin_ops.py +1 -1
  86. mindroot/coreplugins/index/indices/default/index.json +6 -6
  87. mindroot/coreplugins/jwt_auth/middleware.py +47 -1
  88. mindroot/coreplugins/jwt_auth/mod.py +40 -17
  89. mindroot/coreplugins/l8n/__init__.py +6 -0
  90. mindroot/coreplugins/l8n/debug_loader.py +85 -0
  91. mindroot/coreplugins/l8n/debug_middleware.py +74 -0
  92. mindroot/coreplugins/l8n/l8n_constants.py +19 -0
  93. mindroot/coreplugins/l8n/language_detection.py +183 -0
  94. mindroot/coreplugins/l8n/middleware.py +151 -0
  95. mindroot/coreplugins/l8n/mod.py +277 -0
  96. mindroot/coreplugins/l8n/monkey_patch_to_delete.py +186 -0
  97. mindroot/coreplugins/l8n/test_enhanced.py +298 -0
  98. mindroot/coreplugins/l8n/test_l8n.py +95 -0
  99. mindroot/coreplugins/l8n/test_l8n_standalone.py +251 -0
  100. mindroot/coreplugins/l8n/test_middleware.py +272 -0
  101. mindroot/coreplugins/l8n/utils.py +232 -0
  102. mindroot/coreplugins/mcp_/__init__.py +14 -0
  103. mindroot/coreplugins/mcp_/catalog_commands.py +328 -0
  104. mindroot/coreplugins/mcp_/catalog_manager.py +263 -0
  105. mindroot/coreplugins/mcp_/dynamic_commands.py +154 -0
  106. mindroot/coreplugins/mcp_/mcp_manager.py +1031 -0
  107. mindroot/coreplugins/mcp_/mod.py +367 -0
  108. mindroot/coreplugins/mcp_/oauth_storage.py +144 -0
  109. mindroot/coreplugins/mcp_/server_installer.py +79 -0
  110. mindroot/coreplugins/mcp_/setup.py +26 -0
  111. mindroot/coreplugins/mcp_/test_dynamic_commands.py +134 -0
  112. mindroot/coreplugins/mcp_/testmcpclient.py +92 -0
  113. mindroot/coreplugins/persona/mod.py +12 -7
  114. mindroot/coreplugins/signup/templates/signup.jinja2 +1 -1
  115. mindroot/coreplugins/subscriptions/__init__.py +1 -0
  116. mindroot/coreplugins/subscriptions/mod.py +14 -3
  117. mindroot/coreplugins/subscriptions/router.py +3 -0
  118. mindroot/coreplugins/user_service/__init__.py +1 -2
  119. mindroot/coreplugins/user_service/admin_init.py +1 -0
  120. mindroot/coreplugins/user_service/email_service.py +72 -17
  121. mindroot/coreplugins/user_service/mod.py +10 -2
  122. mindroot/coreplugins/user_service/password_reset_service.py +180 -27
  123. mindroot/coreplugins/user_service/router.py +84 -22
  124. mindroot/lib/auth/api_key.py +28 -0
  125. mindroot/lib/cli/plugins.py +94 -0
  126. mindroot/lib/plugins/default_plugin_manifest.json +20 -0
  127. mindroot/lib/plugins/installation.py +5 -5
  128. mindroot/lib/plugins/l8n_static_handler.py +225 -0
  129. mindroot/lib/plugins/loader.py +33 -3
  130. mindroot/lib/plugins/loader_with_l8n.py +281 -0
  131. mindroot/lib/plugins/manifest.py +238 -17
  132. mindroot/lib/providers/commands.py +3 -1
  133. mindroot/lib/route_decorators.py +5 -5
  134. mindroot/lib/templates.py +183 -11
  135. mindroot/lib/utils/merge_arrays.py +1 -1
  136. mindroot/migrate.py +49 -0
  137. mindroot/registry/data_access.py +1 -1
  138. mindroot/server.py +47 -13
  139. mindroot/server_missing_normal_args.py +197 -0
  140. mindroot/server_prev.py +173 -0
  141. {mindroot-9.2.0.dist-info → mindroot-9.5.0.dist-info}/METADATA +7 -2
  142. {mindroot-9.2.0.dist-info → mindroot-9.5.0.dist-info}/RECORD +147 -114
  143. mindroot/coreplugins/admin/static/favicon/about.txt +0 -6
  144. mindroot/coreplugins/admin/static/favicon/android-chrome-512x512.png +0 -0
  145. mindroot/coreplugins/admin/static/favicon/apple-touch-icon.png +0 -0
  146. mindroot/coreplugins/admin/static/favicon/favicon-16x16.png +0 -0
  147. mindroot/coreplugins/admin/static/favicon/favicon-32x32.png +0 -0
  148. mindroot/coreplugins/admin/static/favicon/favicon.ico +0 -0
  149. mindroot/coreplugins/admin/static/favicon/favicon_io (1)/about.txt +0 -6
  150. mindroot/coreplugins/admin/static/favicon/favicon_io (1)/android-chrome-192x192.png +0 -0
  151. mindroot/coreplugins/admin/static/favicon/favicon_io (1)/android-chrome-512x512.png +0 -0
  152. mindroot/coreplugins/admin/static/favicon/favicon_io (1)/apple-touch-icon.png +0 -0
  153. mindroot/coreplugins/admin/static/favicon/favicon_io (1)/favicon-16x16.png +0 -0
  154. mindroot/coreplugins/admin/static/favicon/favicon_io (1)/favicon-32x32.png +0 -0
  155. mindroot/coreplugins/admin/static/favicon/favicon_io (1)/favicon.ico +0 -0
  156. mindroot/coreplugins/admin/static/favicon/favicon_io (1)/site.webmanifest +0 -1
  157. mindroot/coreplugins/admin/static/favicon/logo.png +0 -0
  158. mindroot/coreplugins/admin/static/favicon/site.webmanifest +0 -1
  159. mindroot/coreplugins/admin/static/js/backup/agent-editor.js +0 -186
  160. mindroot/coreplugins/admin/static/js/backup/agent-form.js +0 -1133
  161. mindroot/coreplugins/admin/static/js/backup/agent-list.js +0 -94
  162. mindroot/coreplugins/chat/static/favicon/about.txt +0 -6
  163. mindroot/coreplugins/chat/static/favicon/android-chrome-192x192.png +0 -0
  164. mindroot/coreplugins/chat/static/favicon/android-chrome-512x512.png +0 -0
  165. mindroot/coreplugins/chat/static/favicon/apple-touch-icon.png +0 -0
  166. mindroot/coreplugins/chat/static/favicon/favicon-16x16.png +0 -0
  167. mindroot/coreplugins/chat/static/favicon/favicon-32x32.png +0 -0
  168. mindroot/coreplugins/chat/static/favicon/favicon.ico +0 -0
  169. mindroot/coreplugins/chat/static/favicon/favicon_io (1)/about.txt +0 -6
  170. mindroot/coreplugins/chat/static/favicon/favicon_io (1)/android-chrome-192x192.png +0 -0
  171. mindroot/coreplugins/chat/static/favicon/favicon_io (1)/android-chrome-512x512.png +0 -0
  172. mindroot/coreplugins/chat/static/favicon/favicon_io (1)/apple-touch-icon.png +0 -0
  173. mindroot/coreplugins/chat/static/favicon/favicon_io (1)/favicon-16x16.png +0 -0
  174. mindroot/coreplugins/chat/static/favicon/favicon_io (1)/favicon-32x32.png +0 -0
  175. mindroot/coreplugins/chat/static/favicon/favicon_io (1)/favicon.ico +0 -0
  176. mindroot/coreplugins/chat/static/favicon/favicon_io (1)/site.webmanifest +0 -1
  177. mindroot/coreplugins/chat/static/favicon/logo.png +0 -0
  178. mindroot/coreplugins/chat/static/favicon/site.webmanifest +0 -1
  179. mindroot/coreplugins/index/default.json +0 -76
  180. mindroot/coreplugins/user_service/file_trigger_service.py +0 -72
  181. mindroot/coreplugins/user_service/hooks.py +0 -23
  182. /mindroot/coreplugins/{admin/static/favicon/android-chrome-192x192.png → home/static/imgs/backuplogo.png} +0 -0
  183. {mindroot-9.2.0.dist-info → mindroot-9.5.0.dist-info}/WHEEL +0 -0
  184. {mindroot-9.2.0.dist-info → mindroot-9.5.0.dist-info}/entry_points.txt +0 -0
  185. {mindroot-9.2.0.dist-info → mindroot-9.5.0.dist-info}/licenses/LICENSE +0 -0
  186. {mindroot-9.2.0.dist-info → mindroot-9.5.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,232 @@
1
+ # need to import Path
2
+ from pathlib import Path
3
+ from .l8n_constants import *
4
+ import json
5
+ import re
6
+ import logging
7
+
8
+ # Set up logging for l8n warnings
9
+ logger = logging.getLogger('l8n')
10
+ logger.setLevel(logging.WARNING)
11
+
12
+ # Create console handler with formatting
13
+ if not logger.handlers:
14
+ console_handler = logging.StreamHandler()
15
+ console_handler.setLevel(logging.WARNING)
16
+ formatter = logging.Formatter('\033[91m[L8N WARNING]\033[0m %(message)s')
17
+ console_handler.setFormatter(formatter)
18
+ logger.addHandler(console_handler)
19
+
20
+ def extract_plugin_root(absolute_path: str) -> str:
21
+ """
22
+ Extract the plugin root from an absolute path.
23
+
24
+ For core plugins: everything after 'coreplugins/'
25
+ For external plugins: everything after 'src/[plugin_name]/'
26
+
27
+ Examples:
28
+ - /files/mindroot/src/mindroot/coreplugins/chat/templates/chat.jinja2 -> chat/templates/chat.jinja2
29
+ - /some/path/src/my_plugin/templates/page.jinja2 -> my_plugin/templates/page.jinja2
30
+ """
31
+ path = Path(absolute_path)
32
+ parts = path.parts
33
+
34
+ # Find coreplugins in the path
35
+ if 'coreplugins' in parts:
36
+ coreplugins_idx = parts.index('coreplugins')
37
+ if coreplugins_idx + 1 < len(parts):
38
+ return '/'.join(parts[coreplugins_idx + 1:])
39
+
40
+ # Find src/[plugin_name] pattern for external plugins
41
+ for i, part in enumerate(parts):
42
+ if part == 'src' and i + 1 < len(parts):
43
+ # Check if this looks like a plugin (has templates, static, etc.)
44
+ potential_plugin = parts[i + 1]
45
+ if i + 2 < len(parts): # Has more path after src/plugin_name
46
+ return '/'.join(parts[i + 1:])
47
+
48
+ # Fallback: return the filename
49
+ return path.name
50
+
51
+ def extract_translation_keys(content: str) -> set:
52
+ """
53
+ Extract all __TRANSLATE_key__ placeholders from content.
54
+
55
+ Args:
56
+ content: Content to scan for translation keys
57
+
58
+ Returns:
59
+ Set of translation keys found in the content
60
+ """
61
+ pattern = r'__TRANSLATE_([a-z0-9_]+)__'
62
+ matches = re.findall(pattern, content)
63
+ return set(matches)
64
+
65
+ def replace_placeholders(content: str, language: str, plugin_path: str = None) -> str:
66
+ """
67
+ Replace __TRANSLATE_key__ placeholders with actual translations.
68
+
69
+ This function now validates that ALL required translations are available
70
+ for the specified language. If any translations are missing, it logs
71
+ a strong warning and returns None to indicate fallback is needed.
72
+
73
+ Args:
74
+ content: Template content with placeholders
75
+ language: Language code for translations
76
+ plugin_path: Path to the localized file (used to determine which plugin's translations to use)
77
+
78
+ Returns:
79
+ Content with placeholders replaced by translations, or None if translations are incomplete
80
+ """
81
+ if not plugin_path:
82
+ # No plugin path provided, return content unchanged
83
+ logger.warning("No plugin path provided for translation replacement.")
84
+ return content
85
+
86
+ try:
87
+ # Extract all translation keys from the content
88
+ required_keys = extract_translation_keys(content)
89
+
90
+ if not required_keys:
91
+ # No translation keys found, return content as-is
92
+ return content
93
+
94
+ # Extract plugin name from the localized file path
95
+ # Path format: .../localized_files/[coreplugins|external_plugins]/[plugin_name]/...
96
+ path_parts = Path(plugin_path).parts
97
+
98
+ # Find 'localized_files' in the path
99
+ if 'localized_files' in path_parts:
100
+ idx = path_parts.index('localized_files')
101
+ if idx + 2 < len(path_parts):
102
+ # Get plugin name (should be at idx+2)
103
+ plugin_type = path_parts[idx + 1] # 'coreplugins' or 'external_plugins'
104
+ plugin_name = path_parts[idx + 2]
105
+
106
+ # Load translations for this plugin
107
+ translations_path = TRANSLATIONS_DIR / plugin_type / plugin_name / "translations.json"
108
+ plugin_translations = {}
109
+ if translations_path.exists():
110
+ try:
111
+ with open(translations_path, 'r', encoding='utf-8') as f:
112
+ plugin_translations = json.load(f)
113
+ except Exception as e:
114
+ logger.warning(f"Could not load translations from {translations_path}: {e}")
115
+ return None # Fallback to original file
116
+
117
+ if language in plugin_translations:
118
+ translations = plugin_translations[language]
119
+
120
+ # Check if ALL required translations are available
121
+ missing_keys = required_keys - set(translations.keys())
122
+
123
+ if missing_keys:
124
+ # Some translations are missing - log strong warning and return None
125
+ missing_list = ', '.join(sorted(missing_keys))
126
+ logger.warning(
127
+ f"\n" +
128
+ f"="*80 + "\n" +
129
+ f"MISSING TRANSLATIONS DETECTED!\n" +
130
+ f"Plugin: {plugin_name}\n" +
131
+ f"Language: {language}\n" +
132
+ f"File: {plugin_path}\n" +
133
+ f"Missing keys: {missing_list}\n" +
134
+ f"Falling back to original file to avoid showing placeholders.\n" +
135
+ f"="*80
136
+ )
137
+ return None # Signal that fallback is needed
138
+
139
+ # All translations are available - proceed with replacement
140
+ def replace_match(match):
141
+ key = match.group(1)
142
+ return translations.get(key, match.group(0)) # This shouldn't happen now
143
+
144
+ return re.sub(r'__TRANSLATE_([a-z0-9_]+)__', replace_match, content)
145
+ else:
146
+ # No translations for this language
147
+ logger.warning(
148
+ f"\n" +
149
+ f"="*80 + "\n" +
150
+ f"NO TRANSLATIONS FOR LANGUAGE!\n" +
151
+ f"Plugin: {plugin_name}\n" +
152
+ f"Language: {language}\n" +
153
+ f"File: {plugin_path}\n" +
154
+ f"Required keys: {', '.join(sorted(required_keys))}\n" +
155
+ f"Falling back to original file.\n" +
156
+ f"="*80
157
+ )
158
+ return None # Signal that fallback is needed
159
+ else:
160
+ logger.warning(f"Could not extract plugin info from path: {plugin_path}")
161
+ return None
162
+ else:
163
+ logger.warning(f"Path does not contain 'localized_files': {plugin_path}")
164
+ return None
165
+
166
+ except Exception as e:
167
+ logger.warning(f"Error in replace_placeholders: {e}")
168
+ return None # Fallback to original file on any error
169
+
170
+ def get_localized_file_path(original_path: str) -> Path:
171
+ """
172
+ Convert an original file path to its localized version path.
173
+
174
+ Examples:
175
+ - chat/templates/chat.jinja2 -> localized_files/coreplugins/chat/templates/chat.i18n.jinja2
176
+ - my_plugin/templates/page.jinja2 -> localized_files/external_plugins/my_plugin/templates/page.i18n.jinja2
177
+ """
178
+ plugin_root = extract_plugin_root(original_path)
179
+ path = Path(plugin_root)
180
+
181
+ # Determine if this is a core plugin or external plugin based on the absolute path
182
+ if 'coreplugins' in original_path:
183
+ base_dir = LOCALIZED_FILES_DIR / "coreplugins"
184
+ else:
185
+ base_dir = LOCALIZED_FILES_DIR / "external_plugins"
186
+
187
+ # Add .i18n before the file extension
188
+ stem = path.stem
189
+ suffix = path.suffix
190
+ new_filename = f"{stem}.i18n{suffix}"
191
+
192
+ localized_path = base_dir / path.parent / new_filename
193
+ return localized_path
194
+
195
+
196
+ def load_plugin_translations(plugin_path: str):
197
+ """Load translations for a specific plugin from disk."""
198
+
199
+ translations_file = get_plugin_translations_path(plugin_path)
200
+
201
+ if translations_file.exists():
202
+ try:
203
+ with open(translations_file, 'r', encoding='utf-8') as f:
204
+ return json.load(f)
205
+ except Exception as e:
206
+ logger.warning(f"Could not load translations from {translations_file}: {e}")
207
+ else:
208
+ logger.warning(f"Translations file does not exist at {translations_file}")
209
+ return {}
210
+
211
+ def get_plugin_translations_path(original_path: str) -> Path:
212
+ """
213
+ Get the path where translations should be stored for a given file.
214
+
215
+ Example:
216
+ /files/mindroot/src/mindroot/coreplugins/check_list/inject/admin.jinja2
217
+ -> translations/coreplugins/check_list/translations.json
218
+ """
219
+ plugin_root = extract_plugin_root(original_path)
220
+
221
+ # Determine if this is a core plugin or external plugin based on the absolute path
222
+ if 'coreplugins' in original_path:
223
+ base_dir = TRANSLATIONS_DIR / "coreplugins"
224
+ else:
225
+ base_dir = TRANSLATIONS_DIR / "external_plugins"
226
+
227
+ # Get just the plugin name (first part of plugin_root)
228
+ plugin_name = Path(plugin_root).parts[0]
229
+
230
+ return base_dir / plugin_name / "translations.json"
231
+
232
+
@@ -0,0 +1,14 @@
1
+ # This import is required for the plugin to load properly
2
+ from .mod import *
3
+
4
+ # Import catalog features
5
+ try:
6
+ from .catalog_commands import *
7
+ except ImportError:
8
+ pass
9
+
10
+ # Import additional commands
11
+ try:
12
+ from .additional_commands import *
13
+ except ImportError:
14
+ pass
@@ -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
+ }