mindroot 9.3.0__py3-none-any.whl → 9.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +63 -0
  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-old.js +385 -0
  35. mindroot/coreplugins/admin/static/js/registry-manager-publish-old-delete.js +166 -0
  36. mindroot/coreplugins/admin/static/js/registry-manager.js +351 -0
  37. mindroot/coreplugins/admin/static/js/registry-publish-section.js +377 -0
  38. mindroot/coreplugins/admin/static/js/registry-search-section.js +400 -0
  39. mindroot/coreplugins/admin/static/js/registry-search-section.js.bak +3 -0
  40. mindroot/coreplugins/admin/static/js/registry-settings.js +69 -0
  41. mindroot/coreplugins/admin/static/js/registry-shared-services.js +857 -0
  42. mindroot/coreplugins/admin/static/js/registry-simple-sections.js +85 -0
  43. mindroot/coreplugins/admin/static/js/secure-widget-manager.js +438 -0
  44. mindroot/coreplugins/admin/static/logo.png +0 -0
  45. mindroot/coreplugins/admin/templates/admin.jinja2 +275 -110
  46. mindroot/coreplugins/agent/Assistant/agent.json +27 -11
  47. mindroot/coreplugins/agent/agent.py +2 -2
  48. mindroot/coreplugins/agent/command_parser.py +25 -10
  49. mindroot/coreplugins/agent/templates/system.jinja2 +0 -12
  50. mindroot/coreplugins/chat/__init__.py +4 -1
  51. mindroot/coreplugins/chat/router.py +132 -20
  52. mindroot/coreplugins/chat/router_dedup_patch.py +20 -0
  53. mindroot/coreplugins/chat/services.py +31 -1
  54. mindroot/coreplugins/chat/static/css/action-fix.css +32 -0
  55. mindroot/coreplugins/chat/static/css/admin-custom.css +5 -3
  56. mindroot/coreplugins/chat/static/css/dark.css +24 -3
  57. mindroot/coreplugins/chat/static/css/default.css +24 -3
  58. mindroot/coreplugins/chat/static/css/main.css +1 -0
  59. mindroot/coreplugins/chat/static/js/action.js +137 -60
  60. mindroot/coreplugins/chat/static/js/chat-history.js +3 -0
  61. mindroot/coreplugins/chat/static/js/chat.js +59 -16
  62. mindroot/coreplugins/chat/static/js/chat.js.diff +221 -0
  63. mindroot/coreplugins/chat/static/js/chatform.js +2 -2
  64. mindroot/coreplugins/chat/static/site.webmanifest +1 -1
  65. mindroot/coreplugins/chat/templates/chat.jinja2 +3 -3
  66. mindroot/coreplugins/chat/widget_manager.py +139 -0
  67. mindroot/coreplugins/chat/widget_routes.py +287 -0
  68. mindroot/coreplugins/check_list/inject/admin.jinja2 +1 -1
  69. mindroot/coreplugins/email/__init__.py +2 -0
  70. mindroot/coreplugins/email/email_provider.py +2 -2
  71. mindroot/coreplugins/email/mod.py +100 -0
  72. mindroot/coreplugins/email/services.py +5 -3
  73. mindroot/coreplugins/email/smtp_handler.py +9 -3
  74. mindroot/coreplugins/email/test_email_service.py +75 -0
  75. mindroot/coreplugins/env_manager/mod.py +61 -25
  76. mindroot/coreplugins/home/router.py +37 -2
  77. mindroot/coreplugins/home/static/imgs/logo.png +0 -0
  78. mindroot/coreplugins/home/static/imgs/logo.png.bak +0 -0
  79. mindroot/coreplugins/home/static/imgs/logo_teal.png +0 -0
  80. mindroot/coreplugins/home/static/imgs/logo_teal2.png +0 -0
  81. mindroot/coreplugins/home/static/imgs/logo_teal_detailed.png +0 -0
  82. mindroot/coreplugins/home/static/imgs/logo_teal_python.png +0 -0
  83. mindroot/coreplugins/home/templates/home.jinja2 +15 -6
  84. mindroot/coreplugins/index/indices/default/index.json +6 -6
  85. mindroot/coreplugins/jwt_auth/middleware.py +47 -2
  86. mindroot/coreplugins/jwt_auth/mod.py +40 -17
  87. mindroot/coreplugins/l8n/__init__.py +6 -0
  88. mindroot/coreplugins/l8n/debug_loader.py +85 -0
  89. mindroot/coreplugins/l8n/debug_middleware.py +74 -0
  90. mindroot/coreplugins/l8n/l8n_constants.py +19 -0
  91. mindroot/coreplugins/l8n/language_detection.py +183 -0
  92. mindroot/coreplugins/l8n/middleware.py +151 -0
  93. mindroot/coreplugins/l8n/mod.py +277 -0
  94. mindroot/coreplugins/l8n/monkey_patch_to_delete.py +186 -0
  95. mindroot/coreplugins/l8n/test_enhanced.py +298 -0
  96. mindroot/coreplugins/l8n/test_l8n.py +95 -0
  97. mindroot/coreplugins/l8n/test_l8n_standalone.py +251 -0
  98. mindroot/coreplugins/l8n/test_middleware.py +272 -0
  99. mindroot/coreplugins/l8n/utils.py +232 -0
  100. mindroot/coreplugins/mcp_/__init__.py +14 -0
  101. mindroot/coreplugins/mcp_/catalog_commands.py +328 -0
  102. mindroot/coreplugins/mcp_/catalog_manager.py +263 -0
  103. mindroot/coreplugins/mcp_/dynamic_commands.py +154 -0
  104. mindroot/coreplugins/mcp_/mcp_manager.py +1031 -0
  105. mindroot/coreplugins/mcp_/mod.py +367 -0
  106. mindroot/coreplugins/mcp_/oauth_storage.py +144 -0
  107. mindroot/coreplugins/mcp_/server_installer.py +79 -0
  108. mindroot/coreplugins/mcp_/setup.py +26 -0
  109. mindroot/coreplugins/mcp_/test_dynamic_commands.py +134 -0
  110. mindroot/coreplugins/mcp_/testmcpclient.py +92 -0
  111. mindroot/coreplugins/persona/mod.py +12 -7
  112. mindroot/coreplugins/signup/templates/signup.jinja2 +1 -1
  113. mindroot/coreplugins/subscriptions/__init__.py +1 -0
  114. mindroot/coreplugins/subscriptions/mod.py +14 -3
  115. mindroot/coreplugins/subscriptions/router.py +3 -0
  116. mindroot/coreplugins/user_service/__init__.py +1 -2
  117. mindroot/coreplugins/user_service/admin_init.py +1 -0
  118. mindroot/coreplugins/user_service/email_service.py +72 -17
  119. mindroot/coreplugins/user_service/mod.py +10 -2
  120. mindroot/coreplugins/user_service/router.py +2 -0
  121. mindroot/lib/auth/api_key.py +28 -0
  122. mindroot/lib/cli/plugins.py +94 -0
  123. mindroot/lib/plugins/default_plugin_manifest.json +20 -0
  124. mindroot/lib/plugins/installation.py +5 -5
  125. mindroot/lib/plugins/l8n_static_handler.py +225 -0
  126. mindroot/lib/plugins/loader.py +33 -3
  127. mindroot/lib/plugins/loader_with_l8n.py +281 -0
  128. mindroot/lib/plugins/manifest.py +236 -24
  129. mindroot/lib/providers/commands.py +3 -1
  130. mindroot/lib/route_decorators.py +5 -5
  131. mindroot/lib/templates.py +183 -11
  132. mindroot/lib/utils/merge_arrays.py +1 -1
  133. mindroot/migrate.py +39 -20
  134. mindroot/registry/data_access.py +1 -1
  135. mindroot/server.py +42 -13
  136. mindroot/server_missing_normal_args.py +197 -0
  137. mindroot/server_prev.py +173 -0
  138. {mindroot-9.3.0.dist-info → mindroot-9.5.0.dist-info}/METADATA +7 -2
  139. {mindroot-9.3.0.dist-info → mindroot-9.5.0.dist-info}/RECORD +144 -112
  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.5.0.dist-info}/WHEEL +0 -0
  181. {mindroot-9.3.0.dist-info → mindroot-9.5.0.dist-info}/entry_points.txt +0 -0
  182. {mindroot-9.3.0.dist-info → mindroot-9.5.0.dist-info}/licenses/LICENSE +0 -0
  183. {mindroot-9.3.0.dist-info → mindroot-9.5.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,367 @@
1
+ import asyncio
2
+ import os
3
+ import json
4
+ from urllib.parse import parse_qs, urlparse
5
+ import subprocess
6
+ import sys
7
+ import uuid
8
+ from datetime import datetime
9
+ from pathlib import Path
10
+ from typing import Dict, List, Optional, Any
11
+ from contextlib import AsyncExitStack
12
+
13
+ import httpx
14
+ from pydantic import BaseModel
15
+
16
+ from lib.providers.commands import command
17
+ from lib.providers.services import service
18
+ from .mcp_manager import MCPManager, MCPServer
19
+ from .server_installer import MCPServerInstaller
20
+ from .dynamic_commands import MCPDynamicCommands
21
+ from .oauth_storage import MCPTokenStorage
22
+
23
+ try:
24
+ from mcp import ClientSession, StdioServerParameters
25
+ from mcp.client.stdio import stdio_client
26
+ from mcp.client.streamable_http import streamablehttp_client
27
+ from mcp.client.sse import sse_client
28
+ from mcp.client.auth import OAuthClientProvider, TokenStorage
29
+ from mcp.shared.auth import OAuthClientMetadata, OAuthToken, OAuthClientInformationFull
30
+ from pydantic import AnyUrl
31
+ MCP_AVAILABLE = True
32
+ except ImportError:
33
+ # MCP not installed yet
34
+ ClientSession = None
35
+ StdioServerParameters = None
36
+ stdio_client = None
37
+ streamablehttp_client = None
38
+ sse_client = None
39
+ OAuthClientProvider = None
40
+ OAuthClientMetadata = None
41
+ OAuthToken = None
42
+ OAuthClientInformationFull = None
43
+ AnyUrl = None
44
+ MCP_AVAILABLE = False
45
+
46
+ async def handle_redirect(auth_url: str) -> None:
47
+ print(f"Visit: {auth_url}")
48
+
49
+
50
+ async def handle_callback() -> tuple[str, str | None]:
51
+ callback_url = input("Paste callback URL: ")
52
+ params = parse_qs(urlparse(callback_url).query)
53
+ return params["code"][0], params.get("state", [None])[0]
54
+
55
+
56
+ # Global MCP manager instance
57
+ mcp_manager = MCPManager()
58
+
59
+
60
+ @service()
61
+ async def mcp_manager_service(context=None):
62
+ """Service to access the MCP manager"""
63
+ print("returning mcp_manager")
64
+ return mcp_manager
65
+
66
+ @service()
67
+ async def enhanced_mcp_manager_service(context=None):
68
+ """Service to access enhanced MCP manager (same as mcp_manager_service now)"""
69
+ print("returning enhanced MCP manager service")
70
+ return mcp_manager
71
+
72
+
73
+ @command()
74
+ async def mcp_connect(server_name: str, context=None):
75
+ """Connect to an MCP server
76
+
77
+ Example:
78
+ { "mcp_connect": { "server_name": "filesystem" } }
79
+ """
80
+ success = await mcp_manager.connect_server(server_name)
81
+ if success:
82
+ return f"Successfully connected to MCP server: {server_name}"
83
+ else:
84
+ return f"Failed to connect to MCP server: {server_name}"
85
+
86
+
87
+ @command()
88
+ async def mcp_disconnect(server_name: str, context=None):
89
+ """Disconnect from an MCP server
90
+
91
+ Example:
92
+ { "mcp_disconnect": { "server_name": "filesystem" } }
93
+ """
94
+ success = await mcp_manager.disconnect_server(server_name)
95
+ if success:
96
+ return f"Successfully disconnected from MCP server: {server_name}"
97
+ else:
98
+ return f"Failed to disconnect from MCP server: {server_name}"
99
+
100
+
101
+ @command()
102
+ async def mcp_list_servers(context=None):
103
+ """List all configured MCP servers
104
+
105
+ Example:
106
+ { "mcp_list_servers": {} }
107
+ """
108
+ servers = []
109
+ for name, server in mcp_manager.servers.items():
110
+ servers.append({
111
+ "name": name,
112
+ "description": server.description,
113
+ "status": server.status,
114
+ "transport": server.transport,
115
+ "tools_count": len(server.capabilities.get("tools", [])),
116
+ "resources_count": len(server.capabilities.get("resources", [])),
117
+ "prompts_count": len(server.capabilities.get("prompts", []))
118
+ })
119
+ return servers
120
+
121
+
122
+ # Enhanced commands from enhanced_mod.py
123
+ @command()
124
+ async def mcp_enhanced_connect(server_name: str, context=None):
125
+ """Connect to MCP server with enhanced features
126
+
127
+ Example:
128
+ { "mcp_enhanced_connect": { "server_name": "calculator" } }
129
+ """
130
+ success = await mcp_manager.connect_server(server_name)
131
+ if success:
132
+ return f"Successfully connected to {server_name} with enhanced features"
133
+ else:
134
+ return f"Failed to connect to {server_name}"
135
+
136
+
137
+ @command()
138
+ async def mcp_enhanced_disconnect(server_name: str, context=None):
139
+ """Disconnect from MCP server
140
+
141
+ Example:
142
+ { "mcp_enhanced_disconnect": { "server_name": "calculator" } }
143
+ """
144
+ success = await mcp_manager.disconnect_server(server_name)
145
+ if success:
146
+ return f"Successfully disconnected from {server_name}"
147
+ else:
148
+ return f"Failed to disconnect from {server_name}"
149
+
150
+
151
+ @command()
152
+ async def mcp_install_uvx_server(name: str, package: str, description: str = None, context=None):
153
+ """Install and configure a uvx-based MCP server
154
+
155
+ Example:
156
+ { "mcp_install_uvx_server": {
157
+ "name": "calculator",
158
+ "package": "mcp-server-calculator",
159
+ "description": "Calculator server"
160
+ } }
161
+ """
162
+ server = MCPServer(
163
+ name=name,
164
+ description=description or f"MCP server: {name}",
165
+ command="uvx",
166
+ args=[package],
167
+ install_method="uvx",
168
+ install_package=package,
169
+ auto_install=True
170
+ )
171
+
172
+ mcp_manager.add_server(name, server)
173
+ return f"Configured uvx server {name} with package {package}"
174
+
175
+
176
+ @command()
177
+ async def mcp_install_npx_server(name: str, package: str, description: str = None, context=None):
178
+ """Install and configure an npx-based MCP server
179
+
180
+ Example:
181
+ { "mcp_install_npx_server": {
182
+ "name": "github",
183
+ "package": "@modelcontextprotocol/server-github",
184
+ "description": "GitHub server"
185
+ } }
186
+ """
187
+ server = MCPServer(
188
+ name=name,
189
+ description=description or f"MCP server: {name}",
190
+ command="npx",
191
+ args=["-y", package],
192
+ install_method="npx",
193
+ install_package=package,
194
+ auto_install=True
195
+ )
196
+
197
+ mcp_manager.add_server(name, server)
198
+ return f"Configured npx server {name} with package {package}"
199
+
200
+
201
+ @command()
202
+ async def mcp_debug_connection(server_name: str, context=None):
203
+ """Debug MCP server connection and dynamic command registration
204
+
205
+ Example:
206
+ { "mcp_debug_connection": { "server_name": "calculator" } }
207
+ { "mcp_debug_connection": { "server_name": "github" } }
208
+ """
209
+ from .catalog_commands import mcp_catalog_info, mcp_catalog_install_and_run
210
+ from lib.providers.commands import command_manager
211
+ import json
212
+
213
+ debug_info = {
214
+ "server_name": server_name,
215
+ "steps": []
216
+ }
217
+
218
+ # Step 1: Check initial state
219
+ initial_commands = [name for name in command_manager.functions.keys() if name.startswith('mcp_')]
220
+ debug_info["steps"].append({
221
+ "step": "1_initial_state",
222
+ "initial_mcp_commands_count": len(initial_commands),
223
+ "dynamic_commands": mcp_manager.dynamic_commands.get_registered_commands()
224
+ })
225
+
226
+ # Step 2: Get server info
227
+ try:
228
+ server_info = await mcp_catalog_info(server_name)
229
+ debug_info["steps"].append({
230
+ "step": "2_server_info",
231
+ "success": True,
232
+ "server_info": server_info
233
+ })
234
+ except Exception as e:
235
+ debug_info["steps"].append({
236
+ "step": "2_server_info",
237
+ "success": False,
238
+ "error": str(e)
239
+ })
240
+ return debug_info
241
+
242
+ # Step 3: Install and run
243
+ try:
244
+ result = await mcp_catalog_install_and_run(server_name)
245
+ debug_info["steps"].append({
246
+ "step": "3_install_and_run",
247
+ "success": True,
248
+ "result": result
249
+ })
250
+ except Exception as e:
251
+ debug_info["steps"].append({
252
+ "step": "3_install_and_run",
253
+ "success": False,
254
+ "error": str(e)
255
+ })
256
+ return debug_info
257
+
258
+ # Step 4: Check server status
259
+ server_status = {}
260
+ if server_name in mcp_manager.servers:
261
+ server = mcp_manager.servers[server_name]
262
+ server_status = {
263
+ "status": server.status,
264
+ "capabilities": server.capabilities,
265
+ "has_session": server_name in mcp_manager.sessions
266
+ }
267
+
268
+ debug_info["steps"].append({
269
+ "step": "4_server_status",
270
+ "server_found": server_name in mcp_manager.servers,
271
+ "server_status": server_status
272
+ })
273
+
274
+ # Step 5: Check dynamic commands
275
+ final_commands = [name for name in command_manager.functions.keys() if name.startswith('mcp_')]
276
+ new_commands = [cmd for cmd in final_commands if cmd not in initial_commands]
277
+
278
+ debug_info["steps"].append({
279
+ "step": "5_dynamic_commands",
280
+ "total_mcp_commands": len(final_commands),
281
+ "new_commands": new_commands,
282
+ "dynamic_commands_tracker": mcp_manager.dynamic_commands.get_registered_commands()
283
+ })
284
+
285
+ # Step 6: Analyze tools
286
+ if server_name in mcp_manager.servers:
287
+ server = mcp_manager.servers[server_name]
288
+ tools = server.capabilities.get('tools', [])
289
+ tool_analysis = []
290
+
291
+ for tool in tools:
292
+ tool_name = tool.get('name', 'unknown')
293
+ #expected_cmd = f"mcp_{server_name}_{tool_name}"
294
+ expecte_cmd = "mcp_"+tool_name
295
+ is_registered = expected_cmd in command_manager.functions
296
+ tool_analysis.append({
297
+ "tool_name": tool_name,
298
+ "expected_command": expected_cmd,
299
+ "is_registered": is_registered,
300
+ "tool_details": tool
301
+ })
302
+
303
+ debug_info["steps"].append({
304
+ "step": "6_tool_analysis",
305
+ "tools_count": len(tools),
306
+ "tool_analysis": tool_analysis
307
+ })
308
+
309
+ # Step 7: Manual verification if session exists
310
+ if server_name in mcp_manager.sessions:
311
+ try:
312
+ session = mcp_manager.sessions[server_name]
313
+ tools_result = await session.list_tools()
314
+ debug_info["steps"].append({
315
+ "step": "7_manual_verification",
316
+ "success": True,
317
+ "direct_tools_count": len(tools_result.tools),
318
+ "direct_tools": [{
319
+ "name": tool.name,
320
+ "description": tool.description
321
+ } for tool in tools_result.tools]
322
+ })
323
+ except Exception as e:
324
+ debug_info["steps"].append({
325
+ "step": "7_manual_verification",
326
+ "success": False,
327
+ "error": str(e)
328
+ })
329
+
330
+ return debug_info
331
+
332
+ @command()
333
+ async def mcp_check_tools(context=None):
334
+ """Check availability of installation tools
335
+
336
+ Example:
337
+ { "mcp_check_tools": {} }
338
+ """
339
+ tools = await MCPServerInstaller.check_tools()
340
+ return tools
341
+
342
+
343
+ @command()
344
+ async def mcp_list_dynamic_commands(context=None):
345
+ """List dynamically registered MCP commands
346
+
347
+ Example:
348
+ { "mcp_list_dynamic_commands": {} }
349
+ """
350
+ commands = mcp_manager.dynamic_commands.get_registered_commands()
351
+ return {"dynamic_commands": commands, "count": len(commands)}
352
+
353
+
354
+ @command()
355
+ async def mcp_refresh_dynamic_commands(context=None):
356
+ """Refresh dynamic command registration for all connected servers
357
+
358
+ Example:
359
+ { "mcp_refresh_dynamic_commands": {} }
360
+ """
361
+ refreshed = []
362
+ for name, session in mcp_manager.sessions.items():
363
+ tools = await session.list_tools()
364
+ await mcp_manager.dynamic_commands.register_tools(name, tools.tools)
365
+ refreshed.append(f"{name}: {len(tools.tools)} tools")
366
+
367
+ return {"refreshed_servers": refreshed, "total_dynamic_commands": len(mcp_manager.dynamic_commands.get_registered_commands())}
@@ -0,0 +1,144 @@
1
+ """OAuth token storage implementation for MCP servers."""
2
+
3
+ from typing import Optional
4
+ from datetime import datetime
5
+ import json
6
+
7
+ try:
8
+ from mcp.client.auth import TokenStorage
9
+ from mcp.shared.auth import OAuthToken, OAuthClientInformationFull
10
+ except ImportError:
11
+ # Fallback if MCP not available
12
+ class TokenStorage:
13
+ pass
14
+ OAuthToken = None
15
+ OAuthClientInformationFull = None
16
+
17
+
18
+ class MCPTokenStorage(TokenStorage):
19
+ """Token storage implementation for MCP OAuth flows.
20
+
21
+ Stores tokens and client information in the MCP server configuration.
22
+ """
23
+
24
+ def __init__(self, server_name: str, mcp_manager):
25
+ self.server_name = server_name
26
+ self.mcp_manager = mcp_manager
27
+
28
+ async def get_tokens(self) -> Optional[OAuthToken]:
29
+ """Get stored OAuth tokens for the server."""
30
+ if OAuthToken is None:
31
+ return None
32
+
33
+ if self.server_name not in self.mcp_manager.servers:
34
+ return None
35
+
36
+ server = self.mcp_manager.servers[self.server_name]
37
+
38
+ if not server.access_token:
39
+ return None
40
+
41
+ # Parse token expiration
42
+ expires_at = None
43
+ if server.token_expires_at:
44
+ try:
45
+ expires_at = datetime.fromisoformat(server.token_expires_at.replace('Z', '+00:00'))
46
+ except ValueError:
47
+ pass
48
+
49
+ return OAuthToken(
50
+ access_token=server.access_token,
51
+ refresh_token=server.refresh_token,
52
+ expires_at=expires_at,
53
+ scope=" ".join(server.scopes) if server.scopes else None
54
+ )
55
+
56
+ async def set_tokens(self, tokens: OAuthToken) -> None:
57
+ """Store OAuth tokens for the server."""
58
+ if self.server_name not in self.mcp_manager.servers:
59
+ return
60
+
61
+ server = self.mcp_manager.servers[self.server_name]
62
+
63
+ # Update server with token information
64
+ server.access_token = tokens.access_token
65
+ server.refresh_token = tokens.refresh_token
66
+
67
+ if hasattr(tokens, 'expires_at') and tokens.expires_at:
68
+ server.token_expires_at = tokens.expires_at.isoformat()
69
+ elif hasattr(tokens, 'expires_in') and tokens.expires_in:
70
+ # Calculate expires_at from expires_in
71
+ from datetime import datetime, timedelta
72
+ expires_at = datetime.now() + timedelta(seconds=tokens.expires_in)
73
+ server.token_expires_at = expires_at.isoformat()
74
+ else:
75
+ server.token_expires_at = None
76
+
77
+ if tokens.scope:
78
+ server.scopes = tokens.scope.split(" ")
79
+
80
+ # Save configuration
81
+ self.mcp_manager.save_config()
82
+
83
+ async def get_client_info(self) -> Optional[OAuthClientInformationFull]:
84
+ """Get stored OAuth client information."""
85
+ if OAuthClientInformationFull is None:
86
+ return None
87
+
88
+ if self.server_name not in self.mcp_manager.servers:
89
+ return None
90
+
91
+ server = self.mcp_manager.servers[self.server_name]
92
+
93
+ if not server.client_id:
94
+ return None
95
+
96
+ # Create client info from server configuration
97
+ client_info_data = {
98
+ "client_id": server.client_id,
99
+ "client_name": f"MindRoot MCP Client - {server.name}",
100
+ "redirect_uris": [server.redirect_uri] if server.redirect_uri else [],
101
+ "grant_types": ["authorization_code", "refresh_token"],
102
+ "response_types": ["code"],
103
+ "scope": " ".join(server.scopes) if server.scopes else "user"
104
+ }
105
+
106
+ if server.client_secret:
107
+ client_info_data["client_secret"] = server.client_secret
108
+
109
+ return OAuthClientInformationFull(**client_info_data)
110
+
111
+ async def set_client_info(self, client_info: OAuthClientInformationFull) -> None:
112
+ """Store OAuth client information."""
113
+ if self.server_name not in self.mcp_manager.servers:
114
+ return
115
+
116
+ server = self.mcp_manager.servers[self.server_name]
117
+
118
+ # Update server with client information
119
+ server.client_id = client_info.client_id
120
+
121
+ if hasattr(client_info, 'client_secret') and client_info.client_secret:
122
+ server.client_secret = client_info.client_secret
123
+
124
+ if client_info.redirect_uris:
125
+ server.redirect_uri = client_info.redirect_uris[0]
126
+
127
+ if client_info.scope:
128
+ server.scopes = client_info.scope.split(" ")
129
+
130
+ # Save configuration
131
+ self.mcp_manager.save_config()
132
+
133
+ def clear_tokens(self):
134
+ """Clear stored tokens for the server."""
135
+ if self.server_name not in self.mcp_manager.servers:
136
+ return
137
+
138
+ server = self.mcp_manager.servers[self.server_name]
139
+ server.access_token = None
140
+ server.refresh_token = None
141
+ server.token_expires_at = None
142
+
143
+ # Save configuration
144
+ self.mcp_manager.save_config()
@@ -0,0 +1,79 @@
1
+ import subprocess
2
+ import asyncio
3
+ from typing import Dict, Optional
4
+
5
+ class MCPServerInstaller:
6
+ """Handles installation of MCP servers via different methods"""
7
+
8
+ @staticmethod
9
+ async def check_tools() -> Dict[str, bool]:
10
+ """Check if installation tools are available"""
11
+ tools = {}
12
+
13
+ for tool in ['uvx', 'npx', 'npm', 'pip']:
14
+ try:
15
+ result = subprocess.run([tool, '--version'],
16
+ capture_output=True, text=True, timeout=5)
17
+ tools[tool] = result.returncode == 0
18
+ except (FileNotFoundError, subprocess.TimeoutExpired):
19
+ tools[tool] = False
20
+
21
+ return tools
22
+
23
+ @staticmethod
24
+ async def install_with_uvx(package: str, args: list = None) -> bool:
25
+ """Install package with uvx"""
26
+ if args is None:
27
+ args = []
28
+
29
+ try:
30
+ # For uvx, we don't need to install, just check if it can run
31
+ cmd = ['uvx', '--help']
32
+ result = subprocess.run(cmd, capture_output=True, text=True, timeout=10)
33
+ return result.returncode == 0
34
+ except Exception as e:
35
+ print(f"Error with uvx: {e}")
36
+ return False
37
+
38
+ @staticmethod
39
+ async def install_with_npx(package: str, args: list = None) -> bool:
40
+ """Install package with npx (similar to uvx, runs without global install)"""
41
+ if args is None:
42
+ args = []
43
+
44
+ try:
45
+ # For npx, we don't need to install, just check if it can run
46
+ cmd = ['npx', '--help']
47
+ result = subprocess.run(cmd, capture_output=True, text=True, timeout=10)
48
+ return result.returncode == 0
49
+ except Exception as e:
50
+ print(f"Error with npx: {e}")
51
+ return False
52
+
53
+ @staticmethod
54
+ async def install_with_pip(package: str, args: list = None) -> bool:
55
+ """Install package with pip"""
56
+ if args is None:
57
+ args = []
58
+
59
+ try:
60
+ cmd = ['pip', 'install'] + args + [package]
61
+ result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
62
+ return result.returncode == 0
63
+ except Exception as e:
64
+ print(f"Error installing with pip: {e}")
65
+ return False
66
+
67
+ @staticmethod
68
+ async def install_with_npm(package: str, args: list = None) -> bool:
69
+ """Install package with npm"""
70
+ if args is None:
71
+ args = []
72
+
73
+ try:
74
+ cmd = ['npm', 'install', '-g'] + args + [package]
75
+ result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
76
+ return result.returncode == 0
77
+ except Exception as e:
78
+ print(f"Error installing with npm: {e}")
79
+ return False
@@ -0,0 +1,26 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ setup(
4
+ name="mr-mcp",
5
+ version="1.0.0",
6
+ description="Model Context Protocol integration for MindRoot",
7
+ packages=find_packages(where="src"),
8
+ package_dir={"": "src"},
9
+ package_data={
10
+ "mr_mcp": [
11
+ "templates/*.jinja2",
12
+ "inject/*.jinja2",
13
+ "static/js/*.js",
14
+ "static/css/*.css",
15
+ "data/*.json"
16
+ ],
17
+ },
18
+ install_requires=[
19
+ "mcp>=1.9.0",
20
+ "httpx>=0.24.0",
21
+ "pydantic>=2.0.0",
22
+ "aiofiles>=23.0.0",
23
+ "uv>=0.7.0"
24
+ ],
25
+ python_requires=">=3.8",
26
+ )