mcphub-server 3.0.4__tar.gz → 3.3.0__tar.gz

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 (234) hide show
  1. {mcphub_server-3.0.4/mcphub_server.egg-info → mcphub_server-3.3.0}/PKG-INFO +24 -5
  2. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/README.md +21 -4
  3. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/core/__init__.py +0 -7
  4. mcphub_server-3.3.0/core/admin_utils.py +27 -0
  5. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/core/api_keys.py +16 -10
  6. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/core/auth.py +5 -2
  7. mcphub_server-3.3.0/core/config_snippets.py +128 -0
  8. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/core/dashboard/auth.py +102 -10
  9. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/core/dashboard/routes.py +1617 -214
  10. mcphub_server-3.3.0/core/database.py +771 -0
  11. mcphub_server-3.3.0/core/encryption.py +212 -0
  12. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/core/health.py +111 -6
  13. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/core/oauth/client_registry.py +3 -1
  14. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/core/oauth/schemas.py +6 -2
  15. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/core/oauth/server.py +6 -1
  16. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/core/oauth/token_manager.py +13 -3
  17. mcphub_server-3.3.0/core/plugin_visibility.py +59 -0
  18. mcphub_server-3.3.0/core/settings.py +162 -0
  19. mcphub_server-3.3.0/core/site_api.py +674 -0
  20. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/core/site_manager.py +2 -0
  21. mcphub_server-3.3.0/core/templates/dashboard/404.html +59 -0
  22. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/core/templates/dashboard/api-keys/list.html +62 -62
  23. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/core/templates/dashboard/audit-logs/list.html +41 -41
  24. mcphub_server-3.3.0/core/templates/dashboard/auth-login.html +209 -0
  25. mcphub_server-3.3.0/core/templates/dashboard/base.html +324 -0
  26. mcphub_server-3.3.0/core/templates/dashboard/connect.html +299 -0
  27. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/core/templates/dashboard/health/index.html +47 -47
  28. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/core/templates/dashboard/health/projects-partial.html +5 -5
  29. mcphub_server-3.3.0/core/templates/dashboard/index.html +439 -0
  30. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/core/templates/dashboard/login.html +56 -39
  31. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/core/templates/dashboard/oauth-clients/list.html +47 -47
  32. mcphub_server-3.3.0/core/templates/dashboard/partials/head_assets.html +105 -0
  33. mcphub_server-3.3.0/core/templates/dashboard/profile.html +90 -0
  34. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/core/templates/dashboard/projects/detail.html +58 -58
  35. mcphub_server-3.3.0/core/templates/dashboard/projects/list.html +285 -0
  36. mcphub_server-3.3.0/core/templates/dashboard/service.html +185 -0
  37. mcphub_server-3.3.0/core/templates/dashboard/services_list.html +57 -0
  38. mcphub_server-3.3.0/core/templates/dashboard/settings/index.html +323 -0
  39. mcphub_server-3.3.0/core/templates/dashboard/sites/add.html +174 -0
  40. mcphub_server-3.3.0/core/templates/dashboard/sites/edit.html +172 -0
  41. mcphub_server-3.3.0/core/templates/dashboard/sites/list.html +193 -0
  42. mcphub_server-3.3.0/core/templates/dashboard/user-oauth-clients.html +241 -0
  43. mcphub_server-3.3.0/core/templates/oauth/authorize.html +268 -0
  44. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/core/tool_generator.py +9 -4
  45. mcphub_server-3.3.0/core/user_auth.py +483 -0
  46. mcphub_server-3.3.0/core/user_endpoints.py +429 -0
  47. mcphub_server-3.3.0/core/user_keys.py +246 -0
  48. {mcphub_server-3.0.4 → mcphub_server-3.3.0/mcphub_server.egg-info}/PKG-INFO +24 -5
  49. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/mcphub_server.egg-info/SOURCES.txt +38 -2
  50. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/mcphub_server.egg-info/requires.txt +2 -0
  51. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/n8n/handlers/system.py +2 -2
  52. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/n8n/handlers/variables.py +1 -1
  53. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/openpanel/handlers/profiles.py +2 -2
  54. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/supabase/client.py +245 -96
  55. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/supabase/handlers/auth.py +0 -18
  56. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/supabase/handlers/database.py +29 -12
  57. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/supabase/handlers/system.py +8 -8
  58. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/supabase/plugin.py +15 -6
  59. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/woocommerce/plugin.py +20 -6
  60. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/wordpress/client.py +233 -37
  61. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/wordpress/handlers/menus.py +3 -3
  62. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/wordpress/handlers/seo.py +3 -3
  63. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/wordpress/wp_cli.py +70 -29
  64. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/wordpress_advanced/handlers/system.py +3 -3
  65. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/wordpress_advanced/plugin.py +14 -1
  66. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/pyproject.toml +3 -1
  67. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/server.py +578 -81
  68. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/server_multi.py +8 -13
  69. mcphub_server-3.3.0/tests/test_admin_system.py +188 -0
  70. mcphub_server-3.3.0/tests/test_config_snippets.py +159 -0
  71. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/tests/test_dashboard.py +37 -0
  72. mcphub_server-3.3.0/tests/test_database.py +577 -0
  73. mcphub_server-3.3.0/tests/test_dynamic_endpoints_integration.py +115 -0
  74. mcphub_server-3.3.0/tests/test_encryption.py +315 -0
  75. mcphub_server-3.3.0/tests/test_f3_admin_stats.py +17 -0
  76. mcphub_server-3.3.0/tests/test_f3_last_tested.py +77 -0
  77. mcphub_server-3.3.0/tests/test_f3_service_pages.py +35 -0
  78. mcphub_server-3.3.0/tests/test_oauth_basic_auth.py +165 -0
  79. mcphub_server-3.3.0/tests/test_oauth_resource.py +333 -0
  80. mcphub_server-3.3.0/tests/test_oauth_revoke.py +227 -0
  81. mcphub_server-3.3.0/tests/test_plugin_visibility.py +148 -0
  82. mcphub_server-3.3.0/tests/test_site_api.py +353 -0
  83. mcphub_server-3.3.0/tests/test_tenant_isolation.py +40 -0
  84. mcphub_server-3.3.0/tests/test_user_auth.py +444 -0
  85. mcphub_server-3.3.0/tests/test_user_endpoints.py +366 -0
  86. mcphub_server-3.3.0/tests/test_user_keys.py +294 -0
  87. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/tests/test_woocommerce_plugin.py +6 -5
  88. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/tests/test_wordpress_plugin.py +283 -1
  89. mcphub_server-3.0.4/core/site_registry.py +0 -371
  90. mcphub_server-3.0.4/core/templates/dashboard/base.html +0 -227
  91. mcphub_server-3.0.4/core/templates/dashboard/index.html +0 -292
  92. mcphub_server-3.0.4/core/templates/dashboard/projects/list.html +0 -259
  93. mcphub_server-3.0.4/core/templates/dashboard/settings/index.html +0 -210
  94. mcphub_server-3.0.4/core/templates/oauth/authorize.html +0 -170
  95. mcphub_server-3.0.4/core/unified_tools.py +0 -363
  96. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/LICENSE +0 -0
  97. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/core/audit_log.py +0 -0
  98. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/core/context.py +0 -0
  99. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/core/dashboard/__init__.py +0 -0
  100. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/core/endpoints/__init__.py +0 -0
  101. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/core/endpoints/config.py +0 -0
  102. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/core/endpoints/factory.py +0 -0
  103. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/core/endpoints/middleware.py +0 -0
  104. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/core/endpoints/registry.py +0 -0
  105. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/core/i18n.py +0 -0
  106. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/core/middleware/__init__.py +0 -0
  107. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/core/oauth/__init__.py +0 -0
  108. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/core/oauth/csrf.py +0 -0
  109. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/core/oauth/pkce.py +0 -0
  110. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/core/oauth/storage.py +0 -0
  111. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/core/project_manager.py +0 -0
  112. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/core/rate_limiter.py +0 -0
  113. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/core/templates/base.html +0 -0
  114. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/core/templates/oauth/error.html +0 -0
  115. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/core/tool_registry.py +0 -0
  116. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/mcphub_server.egg-info/dependency_links.txt +0 -0
  117. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/mcphub_server.egg-info/entry_points.txt +0 -0
  118. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/mcphub_server.egg-info/top_level.txt +0 -0
  119. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/__init__.py +0 -0
  120. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/appwrite/__init__.py +0 -0
  121. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/appwrite/client.py +0 -0
  122. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/appwrite/handlers/__init__.py +0 -0
  123. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/appwrite/handlers/databases.py +0 -0
  124. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/appwrite/handlers/documents.py +0 -0
  125. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/appwrite/handlers/functions.py +0 -0
  126. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/appwrite/handlers/messaging.py +0 -0
  127. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/appwrite/handlers/storage.py +0 -0
  128. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/appwrite/handlers/system.py +0 -0
  129. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/appwrite/handlers/teams.py +0 -0
  130. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/appwrite/handlers/users.py +0 -0
  131. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/appwrite/plugin.py +0 -0
  132. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/base.py +0 -0
  133. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/directus/__init__.py +0 -0
  134. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/directus/client.py +0 -0
  135. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/directus/handlers/__init__.py +0 -0
  136. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/directus/handlers/access.py +0 -0
  137. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/directus/handlers/automation.py +0 -0
  138. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/directus/handlers/collections.py +0 -0
  139. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/directus/handlers/content.py +0 -0
  140. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/directus/handlers/dashboards.py +0 -0
  141. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/directus/handlers/files.py +0 -0
  142. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/directus/handlers/items.py +0 -0
  143. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/directus/handlers/system.py +0 -0
  144. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/directus/handlers/users.py +0 -0
  145. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/directus/plugin.py +0 -0
  146. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/gitea/__init__.py +0 -0
  147. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/gitea/client.py +0 -0
  148. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/gitea/handlers/__init__.py +0 -0
  149. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/gitea/handlers/issues.py +0 -0
  150. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/gitea/handlers/pull_requests.py +0 -0
  151. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/gitea/handlers/repositories.py +0 -0
  152. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/gitea/handlers/users.py +0 -0
  153. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/gitea/handlers/webhooks.py +0 -0
  154. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/gitea/plugin.py +0 -0
  155. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/gitea/schemas/__init__.py +0 -0
  156. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/gitea/schemas/common.py +0 -0
  157. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/gitea/schemas/issue.py +0 -0
  158. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/gitea/schemas/pull_request.py +0 -0
  159. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/gitea/schemas/repository.py +0 -0
  160. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/gitea/schemas/user.py +0 -0
  161. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/gitea/schemas/webhook.py +0 -0
  162. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/n8n/__init__.py +0 -0
  163. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/n8n/client.py +0 -0
  164. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/n8n/handlers/__init__.py +0 -0
  165. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/n8n/handlers/credentials.py +0 -0
  166. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/n8n/handlers/executions.py +0 -0
  167. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/n8n/handlers/projects.py +0 -0
  168. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/n8n/handlers/tags.py +0 -0
  169. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/n8n/handlers/users.py +0 -0
  170. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/n8n/handlers/workflows.py +0 -0
  171. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/n8n/plugin.py +0 -0
  172. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/openpanel/__init__.py +0 -0
  173. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/openpanel/client.py +0 -0
  174. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/openpanel/handlers/__init__.py +0 -0
  175. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/openpanel/handlers/clients.py +0 -0
  176. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/openpanel/handlers/dashboards.py +0 -0
  177. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/openpanel/handlers/events.py +0 -0
  178. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/openpanel/handlers/export.py +0 -0
  179. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/openpanel/handlers/funnels.py +0 -0
  180. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/openpanel/handlers/projects.py +0 -0
  181. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/openpanel/handlers/reports.py +0 -0
  182. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/openpanel/handlers/system.py +0 -0
  183. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/openpanel/handlers/utils.py +0 -0
  184. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/openpanel/plugin.py +0 -0
  185. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/supabase/__init__.py +0 -0
  186. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/supabase/handlers/__init__.py +0 -0
  187. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/supabase/handlers/admin.py +0 -0
  188. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/supabase/handlers/functions.py +0 -0
  189. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/supabase/handlers/storage.py +0 -0
  190. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/woocommerce/__init__.py +0 -0
  191. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/wordpress/__init__.py +0 -0
  192. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/wordpress/handlers/__init__.py +0 -0
  193. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/wordpress/handlers/comments.py +0 -0
  194. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/wordpress/handlers/coupons.py +0 -0
  195. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/wordpress/handlers/customers.py +0 -0
  196. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/wordpress/handlers/media.py +0 -0
  197. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/wordpress/handlers/orders.py +0 -0
  198. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/wordpress/handlers/posts.py +0 -0
  199. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/wordpress/handlers/products.py +0 -0
  200. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/wordpress/handlers/reports.py +0 -0
  201. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/wordpress/handlers/site.py +0 -0
  202. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/wordpress/handlers/taxonomy.py +0 -0
  203. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/wordpress/handlers/users.py +0 -0
  204. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/wordpress/handlers/wp_cli.py +0 -0
  205. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/wordpress/plugin.py +0 -0
  206. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/wordpress/plugin_old_backup.py +0 -0
  207. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/wordpress/schemas/__init__.py +0 -0
  208. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/wordpress/schemas/common.py +0 -0
  209. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/wordpress/schemas/media.py +0 -0
  210. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/wordpress/schemas/order.py +0 -0
  211. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/wordpress/schemas/post.py +0 -0
  212. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/wordpress/schemas/product.py +0 -0
  213. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/wordpress/schemas/seo.py +0 -0
  214. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/wordpress_advanced/__init__.py +0 -0
  215. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/wordpress_advanced/handlers/__init__.py +0 -0
  216. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/wordpress_advanced/handlers/bulk.py +0 -0
  217. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/wordpress_advanced/handlers/database.py +0 -0
  218. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/wordpress_advanced/schemas/__init__.py +0 -0
  219. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/wordpress_advanced/schemas/bulk.py +0 -0
  220. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/wordpress_advanced/schemas/database.py +0 -0
  221. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/plugins/wordpress_advanced/schemas/system.py +0 -0
  222. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/setup.cfg +0 -0
  223. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/tests/test_api_keys.py +0 -0
  224. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/tests/test_auth.py +0 -0
  225. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/tests/test_auth_logic.py +0 -0
  226. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/tests/test_oauth_client_registry.py +0 -0
  227. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/tests/test_oauth_integration.py +0 -0
  228. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/tests/test_oauth_pkce.py +0 -0
  229. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/tests/test_oauth_schemas.py +0 -0
  230. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/tests/test_oauth_storage.py +0 -0
  231. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/tests/test_oauth_token_manager.py +0 -0
  232. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/tests/test_rate_limiter.py +0 -0
  233. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/tests/test_site_manager.py +0 -0
  234. {mcphub_server-3.0.4 → mcphub_server-3.3.0}/tests/test_tool_registry.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcphub-server
3
- Version: 3.0.4
3
+ Version: 3.3.0
4
4
  Summary: AI-native management hub for WordPress, WooCommerce, and self-hosted services via Model Context Protocol (MCP)
5
5
  Author-email: MCP Hub <contact@mcphub.dev>
6
6
  License-Expression: MIT
@@ -33,7 +33,9 @@ Requires-Dist: docker>=7.0.0
33
33
  Requires-Dist: authlib>=1.5.0
34
34
  Requires-Dist: PyJWT>=2.8.0
35
35
  Requires-Dist: cryptography>=46.0.0
36
+ Requires-Dist: aiosqlite>=0.20.0
36
37
  Requires-Dist: jinja2>=3.1.2
38
+ Requires-Dist: bcrypt>=4.0.0
37
39
  Provides-Extra: dev
38
40
  Requires-Dist: pytest>=7.4.0; extra == "dev"
39
41
  Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
@@ -45,6 +47,8 @@ Requires-Dist: mypy>=1.7.0; extra == "dev"
45
47
  Requires-Dist: pre-commit>=3.5.0; extra == "dev"
46
48
  Dynamic: license-file
47
49
 
50
+ <!-- mcp-name: io.github.airano-ir/mcphub -->
51
+
48
52
  # MCP Hub
49
53
 
50
54
  <div align="center">
@@ -58,7 +62,7 @@ Connect your sites, stores, repos, and databases — manage them all through Cla
58
62
  [![Python 3.11+](https://img.shields.io/badge/python-3.11+-3776ab.svg)](https://www.python.org/)
59
63
  [![PyPI](https://img.shields.io/pypi/v/mcphub-server.svg)](https://pypi.org/project/mcphub-server/)
60
64
  [![Docker](https://img.shields.io/docker/v/airano/mcphub?label=docker)](https://hub.docker.com/r/airano/mcphub)
61
- [![Tests: 290 passing](https://img.shields.io/badge/tests-290%20passing-brightgreen.svg)]()
65
+ [![Tests: 481 passing](https://img.shields.io/badge/tests-481%20passing-brightgreen.svg)]()
62
66
  [![Tools: 596](https://img.shields.io/badge/tools-596-orange.svg)]()
63
67
  [![CI](https://github.com/airano-ir/mcphub/actions/workflows/ci.yml/badge.svg)](https://github.com/airano-ir/mcphub/actions/workflows/ci.yml)
64
68
 
@@ -153,7 +157,20 @@ curl http://localhost:8000/health
153
157
 
154
158
  Open the **web dashboard** in your browser: **http://localhost:8000/dashboard**
155
159
 
156
- You should see the login page. Use your `MASTER_API_KEY` to log in.
160
+ You should see the login page. Log in with your `MASTER_API_KEY` or via **GitHub/Google OAuth** (if configured).
161
+
162
+ ### Try It Now (No Setup Required)
163
+
164
+ **Don't want to self-host?** Try the demo instance at **[mcp.palebluedot.live](https://mcp.palebluedot.live)**:
165
+
166
+ 1. Log in with **GitHub** or **Google**
167
+ 2. Add your sites via the dashboard (My Sites → Add Service)
168
+ 3. Go to **Connect** page — generate config for your AI client
169
+ 4. Copy-paste the config into Claude Desktop, VS Code, or Claude Code
170
+
171
+ Your personal MCP endpoint: `https://mcp.palebluedot.live/u/{your-user-id}/{alias}/mcp`
172
+
173
+ ---
157
174
 
158
175
  ### Configure Your Sites
159
176
 
@@ -342,12 +359,14 @@ MCP Hub supports **Open Dynamic Client Registration** (RFC 7591). ChatGPT can au
342
359
  /appwrite/mcp → Appwrite tools (100 tools)
343
360
  /directus/mcp → Directus tools (100 tools)
344
361
  /project/{alias}/mcp → Per-project endpoint (auto-injects site)
362
+ /u/{user_id}/{alias}/mcp → Per-user endpoint (hosted/OAuth users)
345
363
  ```
346
364
 
347
365
  **Recommendation**: Use plugin-specific endpoints instead of `/mcp` (596 tools) to minimize token usage.
348
366
 
349
367
  | Endpoint | Use Case | Tools |
350
368
  |----------|----------|------:|
369
+ | `/u/{user_id}/{alias}/mcp` | Hosted users (OAuth login) | 22-100 |
351
370
  | `/project/{alias}/mcp` | Single-site workflow (recommended) | 22-100 |
352
371
  | `/{plugin}/mcp` | Multi-site management | 23-101 |
353
372
  | `/mcp` | Admin & discovery only | 596 |
@@ -368,7 +387,7 @@ Some MCP Hub tools require companion WordPress plugins:
368
387
 
369
388
  | Tools | Requirement |
370
389
  |-------|-------------|
371
- | SEO tools (`wordpress_get_post_seo`, etc.) | [SEO API Bridge](wordpress-plugin/seo-api-bridge/) ([Download ZIP](wordpress-plugin/seo-api-bridge.zip)) + Rank Math or Yoast SEO |
390
+ | SEO tools (`wordpress_get_post_seo`, etc.) | [Airano MCP SEO Bridge](https://wordpress.org/plugins/airano-mcp-seo-bridge/) ([GitHub](wordpress-plugin/airano-mcp-seo-bridge/)) + Rank Math or Yoast SEO |
372
391
  | WP-CLI tools (15 tools: `wp_cache_*`, `wp_db_*`, etc.) | Docker socket + `CONTAINER` env var |
373
392
  | WordPress Advanced database/system tools | Docker socket + `CONTAINER` env var |
374
393
  | OpenPanel analytics integration | [OpenPanel Self-Hosted](wordpress-plugin/openpanel-self-hosted/) ([Download ZIP](wordpress-plugin/openpanel-self-hosted.zip)) |
@@ -408,7 +427,7 @@ Without Docker socket, WP-CLI tools return "not available" but all REST API tool
408
427
  # Install with dev dependencies
409
428
  pip install -e ".[dev]"
410
429
 
411
- # Run tests (290 tests)
430
+ # Run tests (481 tests)
412
431
  pytest
413
432
 
414
433
  # Format and lint
@@ -1,3 +1,5 @@
1
+ <!-- mcp-name: io.github.airano-ir/mcphub -->
2
+
1
3
  # MCP Hub
2
4
 
3
5
  <div align="center">
@@ -11,7 +13,7 @@ Connect your sites, stores, repos, and databases — manage them all through Cla
11
13
  [![Python 3.11+](https://img.shields.io/badge/python-3.11+-3776ab.svg)](https://www.python.org/)
12
14
  [![PyPI](https://img.shields.io/pypi/v/mcphub-server.svg)](https://pypi.org/project/mcphub-server/)
13
15
  [![Docker](https://img.shields.io/docker/v/airano/mcphub?label=docker)](https://hub.docker.com/r/airano/mcphub)
14
- [![Tests: 290 passing](https://img.shields.io/badge/tests-290%20passing-brightgreen.svg)]()
16
+ [![Tests: 481 passing](https://img.shields.io/badge/tests-481%20passing-brightgreen.svg)]()
15
17
  [![Tools: 596](https://img.shields.io/badge/tools-596-orange.svg)]()
16
18
  [![CI](https://github.com/airano-ir/mcphub/actions/workflows/ci.yml/badge.svg)](https://github.com/airano-ir/mcphub/actions/workflows/ci.yml)
17
19
 
@@ -106,7 +108,20 @@ curl http://localhost:8000/health
106
108
 
107
109
  Open the **web dashboard** in your browser: **http://localhost:8000/dashboard**
108
110
 
109
- You should see the login page. Use your `MASTER_API_KEY` to log in.
111
+ You should see the login page. Log in with your `MASTER_API_KEY` or via **GitHub/Google OAuth** (if configured).
112
+
113
+ ### Try It Now (No Setup Required)
114
+
115
+ **Don't want to self-host?** Try the demo instance at **[mcp.palebluedot.live](https://mcp.palebluedot.live)**:
116
+
117
+ 1. Log in with **GitHub** or **Google**
118
+ 2. Add your sites via the dashboard (My Sites → Add Service)
119
+ 3. Go to **Connect** page — generate config for your AI client
120
+ 4. Copy-paste the config into Claude Desktop, VS Code, or Claude Code
121
+
122
+ Your personal MCP endpoint: `https://mcp.palebluedot.live/u/{your-user-id}/{alias}/mcp`
123
+
124
+ ---
110
125
 
111
126
  ### Configure Your Sites
112
127
 
@@ -295,12 +310,14 @@ MCP Hub supports **Open Dynamic Client Registration** (RFC 7591). ChatGPT can au
295
310
  /appwrite/mcp → Appwrite tools (100 tools)
296
311
  /directus/mcp → Directus tools (100 tools)
297
312
  /project/{alias}/mcp → Per-project endpoint (auto-injects site)
313
+ /u/{user_id}/{alias}/mcp → Per-user endpoint (hosted/OAuth users)
298
314
  ```
299
315
 
300
316
  **Recommendation**: Use plugin-specific endpoints instead of `/mcp` (596 tools) to minimize token usage.
301
317
 
302
318
  | Endpoint | Use Case | Tools |
303
319
  |----------|----------|------:|
320
+ | `/u/{user_id}/{alias}/mcp` | Hosted users (OAuth login) | 22-100 |
304
321
  | `/project/{alias}/mcp` | Single-site workflow (recommended) | 22-100 |
305
322
  | `/{plugin}/mcp` | Multi-site management | 23-101 |
306
323
  | `/mcp` | Admin & discovery only | 596 |
@@ -321,7 +338,7 @@ Some MCP Hub tools require companion WordPress plugins:
321
338
 
322
339
  | Tools | Requirement |
323
340
  |-------|-------------|
324
- | SEO tools (`wordpress_get_post_seo`, etc.) | [SEO API Bridge](wordpress-plugin/seo-api-bridge/) ([Download ZIP](wordpress-plugin/seo-api-bridge.zip)) + Rank Math or Yoast SEO |
341
+ | SEO tools (`wordpress_get_post_seo`, etc.) | [Airano MCP SEO Bridge](https://wordpress.org/plugins/airano-mcp-seo-bridge/) ([GitHub](wordpress-plugin/airano-mcp-seo-bridge/)) + Rank Math or Yoast SEO |
325
342
  | WP-CLI tools (15 tools: `wp_cache_*`, `wp_db_*`, etc.) | Docker socket + `CONTAINER` env var |
326
343
  | WordPress Advanced database/system tools | Docker socket + `CONTAINER` env var |
327
344
  | OpenPanel analytics integration | [OpenPanel Self-Hosted](wordpress-plugin/openpanel-self-hosted/) ([Download ZIP](wordpress-plugin/openpanel-self-hosted.zip)) |
@@ -361,7 +378,7 @@ Without Docker socket, WP-CLI tools return "not available" but all REST API tool
361
378
  # Install with dev dependencies
362
379
  pip install -e ".[dev]"
363
380
 
364
- # Run tests (290 tests)
381
+ # Run tests (481 tests)
365
382
  pytest
366
383
 
367
384
  # Format and lint
@@ -45,12 +45,10 @@ from core.rate_limiter import RateLimitConfig, RateLimiter, get_rate_limiter
45
45
  from core.site_manager import SiteConfig, SiteManager, get_site_manager
46
46
 
47
47
  # Legacy (kept for backward compatibility, will be removed in v2.0)
48
- from core.site_registry import SiteInfo, SiteRegistry, get_site_registry
49
48
  from core.tool_generator import ToolGenerator
50
49
 
51
50
  # Tool Management (Option B architecture)
52
51
  from core.tool_registry import ToolDefinition, ToolRegistry, get_tool_registry
53
- from core.unified_tools import UnifiedToolGenerator
54
52
 
55
53
  __all__ = [
56
54
  # Authentication
@@ -64,11 +62,6 @@ __all__ = [
64
62
  "SiteManager",
65
63
  "SiteConfig",
66
64
  "get_site_manager",
67
- # Legacy (deprecated)
68
- "SiteRegistry",
69
- "SiteInfo",
70
- "get_site_registry",
71
- "UnifiedToolGenerator",
72
65
  # Tool Management
73
66
  "ToolRegistry",
74
67
  "ToolDefinition",
@@ -0,0 +1,27 @@
1
+ """Admin utility functions for role determination.
2
+
3
+ Centralizes admin email checking so it can be used in OAuth callback,
4
+ dashboard auth, and future admin/user panel unification (F.5+).
5
+ """
6
+
7
+ import os
8
+
9
+
10
+ def is_admin_email(email: str | None) -> bool:
11
+ """Check if an email is in the ADMIN_EMAILS env var list.
12
+
13
+ Args:
14
+ email: Email address to check.
15
+
16
+ Returns:
17
+ True if email matches an admin email (case-insensitive).
18
+ """
19
+ if not email:
20
+ return False
21
+
22
+ admin_emails_raw = os.environ.get("ADMIN_EMAILS", "")
23
+ if not admin_emails_raw.strip():
24
+ return False
25
+
26
+ admin_emails = {e.strip().lower() for e in admin_emails_raw.split(",") if e.strip()}
27
+ return email.strip().lower() in admin_emails
@@ -14,6 +14,8 @@ from dataclasses import asdict, dataclass
14
14
  from datetime import datetime, timedelta
15
15
  from pathlib import Path
16
16
 
17
+ import bcrypt
18
+
17
19
  logger = logging.getLogger(__name__)
18
20
 
19
21
 
@@ -79,7 +81,7 @@ class APIKey:
79
81
  """
80
82
 
81
83
  key_id: str
82
- key_hash: str
84
+ key_hash: str # bcrypt hash (new keys) or SHA-256 hex (legacy)
83
85
  project_id: str
84
86
  scope: Scope
85
87
  created_at: str
@@ -180,8 +182,16 @@ class APIKeyManager:
180
182
  logger.error(f"Failed to save keys: {e}")
181
183
 
182
184
  def _hash_key(self, api_key: str) -> str:
183
- """Hash API key for storage."""
184
- return hashlib.sha256(api_key.encode()).hexdigest()
185
+ """Hash API key for storage using bcrypt."""
186
+ return bcrypt.hashpw(api_key.encode(), bcrypt.gensalt()).decode()
187
+
188
+ def _verify_key(self, api_key: str, key_hash: str) -> bool:
189
+ """Verify API key against stored hash (supports bcrypt and legacy SHA-256)."""
190
+ if key_hash.startswith("$2"):
191
+ # bcrypt hash
192
+ return bcrypt.checkpw(api_key.encode(), key_hash.encode())
193
+ # Legacy SHA-256 fallback
194
+ return hashlib.sha256(api_key.encode()).hexdigest() == key_hash
185
195
 
186
196
  def create_key(
187
197
  self,
@@ -269,11 +279,9 @@ class APIKeyManager:
269
279
  Returns:
270
280
  Optional[str]: key_id if valid, None otherwise
271
281
  """
272
- key_hash = self._hash_key(api_key)
273
-
274
- # Find key by hash
282
+ # Find key by verifying against stored hash (bcrypt or legacy SHA-256)
275
283
  for key_id, key in self.keys.items():
276
- if key.key_hash != key_hash:
284
+ if not self._verify_key(api_key, key.key_hash):
277
285
  continue
278
286
 
279
287
  # Check if valid (not revoked, not expired)
@@ -341,10 +349,8 @@ class APIKeyManager:
341
349
  Returns:
342
350
  Optional[APIKey]: The APIKey object if found, None otherwise
343
351
  """
344
- key_hash = self._hash_key(api_key)
345
-
346
352
  for key_id, key in self.keys.items():
347
- if key.key_hash == key_hash:
353
+ if self._verify_key(api_key, key.key_hash):
348
354
  logger.debug(f"Found API key {key_id} by token")
349
355
  return key
350
356
 
@@ -51,10 +51,13 @@ class AuthManager:
51
51
  Returns:
52
52
  bool: True if valid
53
53
  """
54
- is_valid = secrets.compare_digest(api_key, self.master_api_key)
54
+ if not self.master_api_key:
55
+ return False
56
+
57
+ is_valid = secrets.compare_digest(api_key.strip(), self.master_api_key.strip())
55
58
 
56
59
  if not is_valid:
57
- logger.warning("Invalid API key attempt")
60
+ logger.debug("Master key validation failed for provided token")
58
61
 
59
62
  return is_valid
60
63
 
@@ -0,0 +1,128 @@
1
+ """MCP client configuration snippet generation (Track E.3).
2
+
3
+ Generates copy-paste configuration snippets for connecting AI clients
4
+ (Claude Desktop, Claude Code, Cursor, VS Code, ChatGPT) to per-user
5
+ MCP endpoints.
6
+
7
+ Usage:
8
+ from core.config_snippets import generate_config, get_supported_clients
9
+
10
+ snippet = generate_config(
11
+ base_url="https://mcp.example.com",
12
+ user_id="abc123",
13
+ alias="myblog",
14
+ api_key="mhu_...",
15
+ client_type="claude_desktop",
16
+ )
17
+ """
18
+
19
+ import json
20
+
21
+ # Supported MCP client types
22
+ SUPPORTED_CLIENTS = [
23
+ {
24
+ "id": "claude_connectors",
25
+ "label": "Claude.ai Connectors",
26
+ "description": "claude.ai/customize/connectors",
27
+ },
28
+ {
29
+ "id": "claude_desktop",
30
+ "label": "Claude Desktop",
31
+ "description": "Anthropic's desktop app for Claude",
32
+ },
33
+ {
34
+ "id": "claude_code",
35
+ "label": "Claude Code",
36
+ "description": "Anthropic's CLI for Claude",
37
+ },
38
+ {
39
+ "id": "cursor",
40
+ "label": "Cursor",
41
+ "description": "AI-first code editor",
42
+ },
43
+ {
44
+ "id": "vscode",
45
+ "label": "VS Code",
46
+ "description": "Visual Studio Code with MCP extension",
47
+ },
48
+ {
49
+ "id": "chatgpt",
50
+ "label": "ChatGPT",
51
+ "description": "OpenAI ChatGPT (URL-based)",
52
+ },
53
+ ]
54
+
55
+ # Clients that only need a URL (no JSON config snippet, no transport Note)
56
+ WEB_CLIENTS = {"claude_connectors", "chatgpt"}
57
+
58
+
59
+ def get_supported_clients() -> list[dict[str, str]]:
60
+ """Return the list of supported MCP client types."""
61
+ return SUPPORTED_CLIENTS
62
+
63
+
64
+ def generate_config(
65
+ base_url: str,
66
+ user_id: str,
67
+ alias: str,
68
+ api_key: str,
69
+ client_type: str,
70
+ ) -> str:
71
+ """Generate a configuration snippet for the given MCP client.
72
+
73
+ Args:
74
+ base_url: Public URL of the MCP Hub instance (no trailing slash).
75
+ user_id: User UUID.
76
+ alias: Site alias.
77
+ api_key: User API key (``mhu_...``).
78
+ client_type: One of the supported client type IDs.
79
+
80
+ Returns:
81
+ JSON configuration string ready for copy-paste.
82
+
83
+ Raises:
84
+ ValueError: If client_type is not supported.
85
+ """
86
+ base_url = base_url.rstrip("/")
87
+ endpoint_url = f"{base_url}/u/{user_id}/{alias}/mcp"
88
+ server_name = f"mcphub-{alias}"
89
+
90
+ # Claude Desktop uses streamableHttp; Claude Code, VS Code, Cursor use http
91
+ transport_type = "streamableHttp" if client_type == "claude_desktop" else "http"
92
+
93
+ if client_type in ("claude_desktop", "claude_code"):
94
+ config = {
95
+ "mcpServers": {
96
+ server_name: {
97
+ "type": transport_type,
98
+ "url": endpoint_url,
99
+ "headers": {
100
+ "Authorization": f"Bearer {api_key}",
101
+ },
102
+ }
103
+ }
104
+ }
105
+ return json.dumps(config, indent=2)
106
+
107
+ elif client_type in ("cursor", "vscode"):
108
+ config = {
109
+ "mcp": {
110
+ "servers": {
111
+ server_name: {
112
+ "type": transport_type,
113
+ "url": endpoint_url,
114
+ "headers": {
115
+ "Authorization": f"Bearer {api_key}",
116
+ },
117
+ }
118
+ }
119
+ }
120
+ }
121
+ return json.dumps(config, indent=2)
122
+
123
+ elif client_type in ("claude_connectors", "chatgpt"):
124
+ return endpoint_url
125
+
126
+ else:
127
+ valid = [c["id"] for c in SUPPORTED_CLIENTS]
128
+ raise ValueError(f"Unsupported client type '{client_type}'. Valid: {valid}")
@@ -89,19 +89,30 @@ class DashboardAuth:
89
89
  if not api_key:
90
90
  return False, "", None
91
91
 
92
+ api_key_clean = api_key.strip()
93
+ # Check if master key dashboard login is disabled
94
+ master_login_disabled = (
95
+ os.environ.get("DISABLE_MASTER_KEY_LOGIN", "false").lower() == "true"
96
+ )
97
+
92
98
  # Check master API key (from env var)
93
- if self.master_api_key and secrets.compare_digest(api_key, self.master_api_key):
99
+ if (
100
+ not master_login_disabled
101
+ and self.master_api_key
102
+ and secrets.compare_digest(api_key_clean, self.master_api_key.strip())
103
+ ):
94
104
  return True, "master", None
95
105
 
96
106
  # Check AuthManager's master key (covers auto-generated temp keys)
97
- try:
98
- from core.auth import get_auth_manager
107
+ if not master_login_disabled:
108
+ try:
109
+ from core.auth import get_auth_manager
99
110
 
100
- auth_mgr = get_auth_manager()
101
- if auth_mgr.validate_master_key(api_key):
102
- return True, "master", None
103
- except Exception as e:
104
- logger.debug(f"AuthManager check skipped: {e}")
111
+ auth_mgr = get_auth_manager()
112
+ if auth_mgr.validate_master_key(api_key):
113
+ return True, "master", None
114
+ except Exception as e:
115
+ logger.debug(f"AuthManager check skipped: {e}")
105
116
 
106
117
  # Check project API keys with admin scope
107
118
  try:
@@ -202,6 +213,9 @@ class DashboardAuth:
202
213
  user_type=payload["type"],
203
214
  key_id=payload.get("kid"),
204
215
  )
216
+ except KeyError as e:
217
+ logger.debug(f"Invalid dashboard session payload (missing key): {e}")
218
+ return None
205
219
  except jwt.ExpiredSignatureError:
206
220
  logger.debug("Dashboard session expired")
207
221
  return None
@@ -224,6 +238,27 @@ class DashboardAuth:
224
238
  return None
225
239
  return self.validate_session(token)
226
240
 
241
+ def get_user_session_from_request(self, request: Request) -> dict | None:
242
+ """Extract and validate an OAuth user session from request.
243
+
244
+ Args:
245
+ request: Starlette request object.
246
+
247
+ Returns:
248
+ User session dict (user_id, email, name, role, type)
249
+ or None.
250
+ """
251
+ token = request.cookies.get(self.COOKIE_NAME)
252
+ if not token:
253
+ return None
254
+ try:
255
+ from core.user_auth import get_user_auth
256
+
257
+ user_auth = get_user_auth()
258
+ return user_auth.validate_user_session(token)
259
+ except (RuntimeError, Exception):
260
+ return None
261
+
227
262
  def set_session_cookie(self, response: Response, token: str) -> Response:
228
263
  """
229
264
  Set session cookie on response.
@@ -273,13 +308,15 @@ class DashboardAuth:
273
308
  RedirectResponse to login page if not authenticated, None if OK.
274
309
  """
275
310
  session = self.get_session_from_request(request)
276
- if not session:
311
+ user_session = self.get_user_session_from_request(request)
312
+
313
+ if not session and not user_session:
277
314
  # Store original URL for redirect after login
278
315
  next_url = str(request.url.path)
279
316
  if request.url.query:
280
317
  next_url += f"?{request.url.query}"
281
318
  return RedirectResponse(
282
- url=f"/dashboard/login?next={next_url}",
319
+ url=f"/auth/login?next={next_url}",
283
320
  status_code=303,
284
321
  )
285
322
  return None
@@ -291,3 +328,58 @@ def get_dashboard_auth() -> DashboardAuth:
291
328
  if _dashboard_auth is None:
292
329
  _dashboard_auth = DashboardAuth()
293
330
  return _dashboard_auth
331
+
332
+
333
+ # ── Role-checking helpers ──────────────────────────────────────
334
+
335
+
336
+ def is_admin_session(session) -> bool:
337
+ """Check if session is admin (master key or API key with admin scope).
338
+
339
+ Args:
340
+ session: DashboardSession or OAuth user dict.
341
+
342
+ Returns:
343
+ True if admin session.
344
+ """
345
+ if isinstance(session, DashboardSession):
346
+ return session.user_type in ("master", "api_key")
347
+ if isinstance(session, dict):
348
+ return session.get("type") == "master" or session.get("role") == "admin"
349
+ return False
350
+
351
+
352
+ def get_session_display_info(session) -> dict:
353
+ """Get display info for header/UI.
354
+
355
+ Args:
356
+ session: DashboardSession or OAuth user dict.
357
+
358
+ Returns:
359
+ Dict with name, type, email, avatar keys.
360
+ """
361
+ if isinstance(session, DashboardSession):
362
+ return {"name": "Admin", "type": "admin", "email": None, "avatar": None}
363
+ if isinstance(session, dict):
364
+ session_type = "admin" if session.get("role") == "admin" else "user"
365
+ return {
366
+ "name": session.get("name") or session.get("email", "User"),
367
+ "type": session_type,
368
+ "email": session.get("email"),
369
+ "avatar": None,
370
+ }
371
+ return {"name": "Unknown", "type": "unknown", "email": None, "avatar": None}
372
+
373
+
374
+ def get_session_user_id(session) -> str | None:
375
+ """Get user_id for OAuth sessions, None for admin.
376
+
377
+ Args:
378
+ session: DashboardSession or OAuth user dict.
379
+
380
+ Returns:
381
+ User UUID string or None.
382
+ """
383
+ if isinstance(session, dict):
384
+ return session.get("user_id")
385
+ return None