testmcpy 0.9.0__tar.gz → 0.9.2__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 (204) hide show
  1. {testmcpy-0.9.0/testmcpy.egg-info → testmcpy-0.9.2}/PKG-INFO +8 -1
  2. {testmcpy-0.9.0 → testmcpy-0.9.2}/README.md +7 -0
  3. {testmcpy-0.9.0 → testmcpy-0.9.2}/pyproject.toml +1 -1
  4. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/server/api.py +122 -7
  5. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/src/llm_integration.py +5 -5
  6. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/storage.py +41 -15
  7. {testmcpy-0.9.0 → testmcpy-0.9.2/testmcpy.egg-info}/PKG-INFO +8 -1
  8. {testmcpy-0.9.0 → testmcpy-0.9.2}/LICENSE +0 -0
  9. {testmcpy-0.9.0 → testmcpy-0.9.2}/MANIFEST.in +0 -0
  10. {testmcpy-0.9.0 → testmcpy-0.9.2}/NOTICE +0 -0
  11. {testmcpy-0.9.0 → testmcpy-0.9.2}/setup.cfg +0 -0
  12. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/__init__.py +0 -0
  13. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/__main__.py +0 -0
  14. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/agent/__init__.py +0 -0
  15. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/agent/hooks.py +0 -0
  16. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/agent/models.py +0 -0
  17. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/agent/orchestrator.py +0 -0
  18. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/agent/prompts.py +0 -0
  19. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/agent/tools.py +0 -0
  20. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/analytics.py +0 -0
  21. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/auth_debugger.py +0 -0
  22. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/auth_flow_recorder.py +0 -0
  23. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/cli/__init__.py +0 -0
  24. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/cli/app.py +0 -0
  25. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/cli/commands/__init__.py +0 -0
  26. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/cli/commands/agent.py +0 -0
  27. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/cli/commands/analytics.py +0 -0
  28. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/cli/commands/badge.py +0 -0
  29. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/cli/commands/baseline.py +0 -0
  30. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/cli/commands/bench.py +0 -0
  31. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/cli/commands/conformance.py +0 -0
  32. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/cli/commands/export_db.py +0 -0
  33. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/cli/commands/mcp.py +0 -0
  34. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/cli/commands/metamorphic.py +0 -0
  35. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/cli/commands/multi_env.py +0 -0
  36. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/cli/commands/mutate.py +0 -0
  37. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/cli/commands/push.py +0 -0
  38. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/cli/commands/run.py +0 -0
  39. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/cli/commands/scan.py +0 -0
  40. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/cli/commands/score.py +0 -0
  41. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/cli/commands/server.py +0 -0
  42. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/cli/commands/tools.py +0 -0
  43. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/cli/commands/tui.py +0 -0
  44. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/cli/commands/wizard.py +0 -0
  45. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/config.py +0 -0
  46. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/core/__init__.py +0 -0
  47. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/core/chat_session.py +0 -0
  48. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/core/docs_optimizer.py +0 -0
  49. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/core/mcp_manager.py +0 -0
  50. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/core/tool_comparison.py +0 -0
  51. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/core/tool_discovery.py +0 -0
  52. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/db.py +0 -0
  53. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/error_handlers.py +0 -0
  54. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/evals/__init__.py +0 -0
  55. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/evals/auth_evaluators.py +0 -0
  56. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/evals/base_evaluators.py +0 -0
  57. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/evals/evaluator_packs.py +0 -0
  58. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/evals/security_evaluators.py +0 -0
  59. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/formatters/__init__.py +0 -0
  60. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/formatters/base.py +0 -0
  61. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/formatters/curl.py +0 -0
  62. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/formatters/graphql.py +0 -0
  63. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/formatters/javascript_client.py +0 -0
  64. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/formatters/json_yaml.py +0 -0
  65. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/formatters/protobuf.py +0 -0
  66. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/formatters/python.py +0 -0
  67. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/formatters/python_client.py +0 -0
  68. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/formatters/thrift.py +0 -0
  69. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/formatters/typescript.py +0 -0
  70. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/formatters/typescript_client.py +0 -0
  71. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/llm_profiles.py +0 -0
  72. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/mcp_profiles.py +0 -0
  73. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/migrate_json.py +0 -0
  74. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/models.py +0 -0
  75. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/research/claude_sdk_detailed_exploration.py +0 -0
  76. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/research/claude_sdk_poc.py +0 -0
  77. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/research/claude_sdk_working_poc.py +0 -0
  78. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/research/test_ollama_tools.py +0 -0
  79. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/security/__init__.py +0 -0
  80. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/security/rules.py +0 -0
  81. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/security/scanner.py +0 -0
  82. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/server/__init__.py +0 -0
  83. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/server/auth_middleware.py +0 -0
  84. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/server/helpers/__init__.py +0 -0
  85. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/server/helpers/mcp_config.py +0 -0
  86. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/server/models.py +0 -0
  87. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/server/routers/__init__.py +0 -0
  88. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/server/routers/agent.py +0 -0
  89. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/server/routers/analytics.py +0 -0
  90. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/server/routers/auth.py +0 -0
  91. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/server/routers/compare.py +0 -0
  92. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/server/routers/compatibility.py +0 -0
  93. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/server/routers/generation_logs.py +0 -0
  94. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/server/routers/health.py +0 -0
  95. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/server/routers/llm.py +0 -0
  96. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/server/routers/mcp_profiles.py +0 -0
  97. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/server/routers/metrics.py +0 -0
  98. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/server/routers/results.py +0 -0
  99. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/server/routers/runs.py +0 -0
  100. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/server/routers/search.py +0 -0
  101. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/server/routers/security.py +0 -0
  102. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/server/routers/smoke_reports.py +0 -0
  103. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/server/routers/test_profiles.py +0 -0
  104. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/server/routers/tests.py +0 -0
  105. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/server/routers/tools.py +0 -0
  106. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/server/run_persistence.py +0 -0
  107. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/server/run_registry.py +0 -0
  108. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/server/state.py +0 -0
  109. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/server/websocket.py +0 -0
  110. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/smoke_test.py +0 -0
  111. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/src/__init__.py +0 -0
  112. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/src/baseline.py +0 -0
  113. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/src/ci_gate.py +0 -0
  114. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/src/comparison_runner.py +0 -0
  115. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/src/coverage_analyzer.py +0 -0
  116. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/src/emitters.py +0 -0
  117. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/src/html_report.py +0 -0
  118. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/src/mcp_client.py +0 -0
  119. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/src/metamorphic.py +0 -0
  120. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/src/model_registry.py +0 -0
  121. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/src/models.py +0 -0
  122. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/src/multi_env.py +0 -0
  123. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/src/oauth_flows.py +0 -0
  124. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/src/prompt_mutation.py +0 -0
  125. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/src/report_generator.py +0 -0
  126. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/src/runner_tools.py +0 -0
  127. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/src/schema_diff.py +0 -0
  128. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/src/test_runner.py +0 -0
  129. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/src/token_manager.py +0 -0
  130. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/src/usability_score.py +0 -0
  131. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/test_profiles.py +0 -0
  132. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/README.md +0 -0
  133. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/dist/assets/index-BXP9_Odn.js +0 -0
  134. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/dist/assets/index-D35cfDhp.css +0 -0
  135. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/dist/index.html +0 -0
  136. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/index.html +0 -0
  137. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/package-lock.json +0 -0
  138. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/package.json +0 -0
  139. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/postcss.config.js +0 -0
  140. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/App.jsx +0 -0
  141. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/components/BackgroundRunsIndicator.jsx +0 -0
  142. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/components/Badge.jsx +0 -0
  143. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/components/CommandPalette.jsx +0 -0
  144. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/components/CompareToolsTab.jsx +0 -0
  145. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/components/ConfirmDialog.jsx +0 -0
  146. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/components/EditorStatusBar.jsx +0 -0
  147. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/components/EditorTabStrip.jsx +0 -0
  148. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/components/ErrorAlert.jsx +0 -0
  149. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/components/ErrorBoundary.jsx +0 -0
  150. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/components/LLMProfileSelector.jsx +0 -0
  151. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/components/LoadingSpinner.jsx +0 -0
  152. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/components/MCPProfileSelector.jsx +0 -0
  153. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/components/NotificationProvider.jsx +0 -0
  154. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/components/OptimizeDocsModal.jsx +0 -0
  155. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/components/OutputDiff.jsx +0 -0
  156. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/components/ParameterCard.jsx +0 -0
  157. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/components/SchemaCodeViewer.jsx +0 -0
  158. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/components/SkeletonLoader.jsx +0 -0
  159. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/components/StreamingLogViewer.jsx +0 -0
  160. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/components/TestGenerationModal.jsx +0 -0
  161. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/components/TestProfileSelector.jsx +0 -0
  162. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/components/TestResultPanel.jsx +0 -0
  163. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/components/TestStatusIndicator.jsx +0 -0
  164. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/components/ToolCallTimeline.jsx +0 -0
  165. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/components/ToolComparison.jsx +0 -0
  166. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/components/ToolDebugModal.jsx +0 -0
  167. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/components/TraceView.jsx +0 -0
  168. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/components/TypeBadge.jsx +0 -0
  169. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/components/Wizard.jsx +0 -0
  170. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/components/__tests__/OutputDiff.test.jsx +0 -0
  171. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/contexts/TestRunContext.jsx +0 -0
  172. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/contexts/ThemeContext.jsx +0 -0
  173. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/hooks/useEditorTheme.js +0 -0
  174. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/hooks/useKeyboardShortcuts.js +0 -0
  175. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/hooks/useSafeFetch.js +0 -0
  176. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/index.css +0 -0
  177. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/main.jsx +0 -0
  178. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/pages/AuthDebugger.jsx +0 -0
  179. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/pages/ChatInterface.jsx +0 -0
  180. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/pages/Configuration.jsx +0 -0
  181. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/pages/GenerationHistory.jsx +0 -0
  182. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/pages/LLMProfiles.jsx +0 -0
  183. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/pages/MCPExplorer.jsx +0 -0
  184. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/pages/MCPProfiles.jsx +0 -0
  185. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/pages/Performance.jsx +0 -0
  186. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/pages/ProfilesManager.jsx +0 -0
  187. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/pages/Reports.jsx +0 -0
  188. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/pages/SecurityDashboard.jsx +0 -0
  189. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/pages/Servers.jsx +0 -0
  190. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/pages/TestManager.jsx +0 -0
  191. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/pages/__tests__/ChatInterface.test.jsx +0 -0
  192. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/pages/__tests__/Performance.test.jsx +0 -0
  193. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/test-setup.js +0 -0
  194. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/utils/__tests__/formatConverters.test.js +0 -0
  195. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/utils/formatConverters.js +0 -0
  196. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/src/utils/formatters.js +0 -0
  197. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/tailwind.config.js +0 -0
  198. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/vite.config.js +0 -0
  199. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy/ui/vitest.config.js +0 -0
  200. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy.egg-info/SOURCES.txt +0 -0
  201. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy.egg-info/dependency_links.txt +0 -0
  202. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy.egg-info/entry_points.txt +0 -0
  203. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy.egg-info/requires.txt +0 -0
  204. {testmcpy-0.9.0 → testmcpy-0.9.2}/testmcpy.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: testmcpy
3
- Version: 0.9.0
3
+ Version: 0.9.2
4
4
  Summary: A comprehensive testing framework for validating LLM tool calling capabilities with MCP services
5
5
  Author: Amin Ghadersohi
6
6
  License-Expression: Apache-2.0
@@ -351,6 +351,13 @@ profiles:
351
351
 
352
352
  The setup command is **idempotent** — safe to run multiple times. Use `--force` to overwrite existing files.
353
353
 
354
+ **`TESTMCPY_CHAT_OAUTH_LOGIN`** (default `true`): when a chat message hits an
355
+ OAuth (`oauth_auto_discover`) MCP profile with no cached token, the server opens
356
+ the interactive browser OAuth flow and retries. This assumes a browser is
357
+ available on the machine running the server — in headless deployments set
358
+ `TESTMCPY_CHAT_OAUTH_LOGIN=false` so the request fails fast with a clear error
359
+ instead of blocking on a login that can never complete.
360
+
354
361
  ### 2. Explore Your MCP Service
355
362
 
356
363
  ```bash
@@ -271,6 +271,13 @@ profiles:
271
271
 
272
272
  The setup command is **idempotent** — safe to run multiple times. Use `--force` to overwrite existing files.
273
273
 
274
+ **`TESTMCPY_CHAT_OAUTH_LOGIN`** (default `true`): when a chat message hits an
275
+ OAuth (`oauth_auto_discover`) MCP profile with no cached token, the server opens
276
+ the interactive browser OAuth flow and retries. This assumes a browser is
277
+ available on the machine running the server — in headless deployments set
278
+ `TESTMCPY_CHAT_OAUTH_LOGIN=false` so the request fails fast with a clear error
279
+ instead of blocking on a login that can never complete.
280
+
274
281
  ### 2. Explore Your MCP Service
275
282
 
276
283
  ```bash
@@ -93,7 +93,7 @@ testmcpy = [
93
93
 
94
94
  [project]
95
95
  name = "testmcpy"
96
- version = "0.9.0"
96
+ version = "0.9.2"
97
97
  description = "A comprehensive testing framework for validating LLM tool calling capabilities with MCP services"
98
98
  authors = [{name = "Amin Ghadersohi"}]
99
99
  license = "Apache-2.0"
@@ -151,6 +151,22 @@ def _get_init_lock(cache_key: str) -> asyncio.Lock:
151
151
  return _client_init_locks[cache_key]
152
152
 
153
153
 
154
+ def _primary_mcp_provider_kwargs(
155
+ clients_to_use: list[tuple[str, str, MCPClient]],
156
+ ) -> dict[str, Any]:
157
+ """mcp_url/auth kwargs from the FIRST selected MCP client.
158
+
159
+ SDK providers support a single MCP server; the Chat UI sends exactly one
160
+ "profileId:mcpName". Without these kwargs the providers fall back to the
161
+ DEFAULT profile's URL/auth, breaking chat for any other selected profile.
162
+ create_llm_provider filters these out for providers that don't accept them.
163
+ """
164
+ if not clients_to_use:
165
+ return {}
166
+ _profile_id, _mcp_name, client = clients_to_use[0]
167
+ return {"mcp_url": client.base_url, "auth": client.auth_config}
168
+
169
+
154
170
  async def get_mcp_clients_for_profile(profile_id: str) -> list[tuple[str, MCPClient]]:
155
171
  """
156
172
  Get or create MCP clients for all MCP servers in a profile.
@@ -299,12 +315,15 @@ async def get_mcp_client_for_server(profile_id: str, mcp_name: str) -> MCPClient
299
315
  return client
300
316
 
301
317
 
302
- async def clear_cached_client(cache_key: str) -> bool:
318
+ async def clear_cached_client(cache_key: str, record_failure: bool = True) -> bool:
303
319
  """
304
320
  Clear a cached MCP client by its cache key.
305
321
 
306
322
  Args:
307
323
  cache_key: Cache key in format "{profile_id}:{mcp_name}"
324
+ record_failure: When True (default), throttle the next reconnect via
325
+ back-off. Pass False for deliberate re-initialization (e.g. an
326
+ interactive OAuth re-login) where an immediate reconnect is wanted.
308
327
 
309
328
  Returns:
310
329
  True if a client was cleared, False if no client was cached
@@ -313,8 +332,9 @@ async def clear_cached_client(cache_key: str) -> bool:
313
332
 
314
333
  client = mcp_clients.pop(cache_key, None)
315
334
  if client:
316
- # Record a failure so the next reconnect is throttled via back-off.
317
- _record_failure(cache_key)
335
+ if record_failure:
336
+ # Record a failure so the next reconnect is throttled via back-off.
337
+ _record_failure(cache_key)
318
338
  try:
319
339
  await client.close()
320
340
  print(f"Cleared cached client '{cache_key}'")
@@ -324,6 +344,69 @@ async def clear_cached_client(cache_key: str) -> bool:
324
344
  return False
325
345
 
326
346
 
347
+ # Marker substring of the ValueError raised by BaseSDKProvider when an
348
+ # oauth_auto_discover profile has no cached token (see
349
+ # llm_integration.BaseSDKProvider._resolve_mcp_bearer_token).
350
+ _OAUTH_TOKEN_ERROR = "No usable cached OAuth token"
351
+
352
+
353
+ def _chat_oauth_login_enabled() -> bool:
354
+ """Feature flag for interactive OAuth login during chat (default ON).
355
+
356
+ Disable with TESTMCPY_CHAT_OAUTH_LOGIN=false (or 0/no). Read at call time
357
+ so tests can monkeypatch the environment.
358
+ """
359
+ return os.environ.get("TESTMCPY_CHAT_OAUTH_LOGIN", "true").strip().lower() not in (
360
+ "0",
361
+ "false",
362
+ "no",
363
+ )
364
+
365
+
366
+ async def _relogin_oauth_servers(server_keys: list[str]) -> dict[str, MCPClient]:
367
+ """Deliberate interactive re-auth for the given "profileId:mcpName" keys.
368
+
369
+ Drops cached clients WITHOUT recording back-off, clears any pre-existing
370
+ back-off state, and re-initializes. MCPClient.initialize() with
371
+ oauth_auto_discover opens the browser OAuth flow and caches the token via
372
+ fastmcp FileTokenStorage; duplicate popups are prevented by the per-key
373
+ init locks.
374
+
375
+ Returns the fresh clients keyed by cache key so callers can replace any
376
+ references to the old, now-closed client objects.
377
+ """
378
+ new_clients: dict[str, MCPClient] = {}
379
+ for cache_key in server_keys:
380
+ await clear_cached_client(cache_key, record_failure=False)
381
+ _clear_failure(cache_key) # earlier failures must not block deliberate re-auth
382
+ profile_id, mcp_name = cache_key.split(":", 1)
383
+ client = await get_mcp_client_for_server(profile_id, mcp_name)
384
+ if client:
385
+ new_clients[cache_key] = client
386
+ return new_clients
387
+
388
+
389
+ def _refresh_client_refs(
390
+ new_clients: dict[str, MCPClient],
391
+ clients_to_use: list[tuple[str, str, MCPClient]],
392
+ tool_to_client: dict[str, tuple[MCPClient, str, str]],
393
+ ) -> tuple[list[tuple[str, str, MCPClient]], dict[str, tuple[MCPClient, str, str]]]:
394
+ """Swap re-logged-in clients into the chat endpoints' lookup structures.
395
+
396
+ After _relogin_oauth_servers the old client objects are closed; tool
397
+ execution through tool_to_client must use the replacements.
398
+ """
399
+ refreshed_clients = [
400
+ (pid, name, new_clients.get(f"{pid}:{name}", client))
401
+ for pid, name, client in clients_to_use
402
+ ]
403
+ refreshed_tools = {
404
+ tool: (new_clients.get(f"{pid}:{name}", client), pid, name)
405
+ for tool, (client, pid, name) in tool_to_client.items()
406
+ }
407
+ return refreshed_clients, refreshed_tools
408
+
409
+
327
410
  def is_auth_error(error_msg: str) -> bool:
328
411
  """Check if an error message indicates an authentication failure."""
329
412
  error_lower = error_msg.lower()
@@ -936,9 +1019,25 @@ async def chat(request: ChatRequest) -> ChatResponse:
936
1019
  provider_kwargs = {}
937
1020
  if api_key:
938
1021
  provider_kwargs["api_key"] = api_key
939
- llm_provider = create_llm_provider(provider, model, **provider_kwargs)
1022
+ provider_kwargs.update(_primary_mcp_provider_kwargs(clients_to_use))
940
1023
  print("[Chat] Initializing LLM provider...")
941
- await llm_provider.initialize()
1024
+ try:
1025
+ llm_provider = create_llm_provider(provider, model, **provider_kwargs)
1026
+ await llm_provider.initialize()
1027
+ except ValueError as e:
1028
+ if not (_chat_oauth_login_enabled() and _OAUTH_TOKEN_ERROR in str(e)):
1029
+ raise
1030
+ print("[Chat] No cached OAuth token; triggering interactive OAuth login...")
1031
+ new_clients = await _relogin_oauth_servers(accessed_servers)
1032
+ # The old client objects are closed now — swap in the replacements
1033
+ # so tool execution doesn't hit a closed client.
1034
+ clients_to_use, tool_to_client = _refresh_client_refs(
1035
+ new_clients, clients_to_use, tool_to_client
1036
+ )
1037
+ provider_kwargs.update(_primary_mcp_provider_kwargs(clients_to_use))
1038
+ llm_provider = create_llm_provider(provider, model, **provider_kwargs)
1039
+ # Single retry; a second failure falls to the existing handlers.
1040
+ await llm_provider.initialize()
942
1041
  print(
943
1042
  f"[Chat] LLM provider initialized. Generating response with {len(all_tools)} tools..."
944
1043
  )
@@ -1209,8 +1308,24 @@ async def chat_stream(request: ChatRequest):
1209
1308
  provider_kwargs: dict = {}
1210
1309
  if api_key:
1211
1310
  provider_kwargs["api_key"] = api_key
1212
- llm_provider = create_llm_provider(provider, model, **provider_kwargs)
1213
- await llm_provider.initialize()
1311
+ provider_kwargs.update(_primary_mcp_provider_kwargs(clients_to_use))
1312
+ try:
1313
+ llm_provider = create_llm_provider(provider, model, **provider_kwargs)
1314
+ await llm_provider.initialize()
1315
+ except ValueError as e:
1316
+ if not (_chat_oauth_login_enabled() and _OAUTH_TOKEN_ERROR in str(e)):
1317
+ raise
1318
+ yield send_event("status", "Waiting for OAuth login in browser...")
1319
+ new_clients = await _relogin_oauth_servers(accessed_servers)
1320
+ # The old client objects are closed now — swap in the replacements
1321
+ # so tool execution doesn't hit a closed client.
1322
+ clients_to_use, tool_to_client = _refresh_client_refs(
1323
+ new_clients, clients_to_use, tool_to_client
1324
+ )
1325
+ provider_kwargs.update(_primary_mcp_provider_kwargs(clients_to_use))
1326
+ llm_provider = create_llm_provider(provider, model, **provider_kwargs)
1327
+ # Single retry; a second failure falls to the existing handlers.
1328
+ await llm_provider.initialize()
1214
1329
 
1215
1330
  # --- Detect if provider is SDK-based (handles its own agentic loop) ---
1216
1331
  from testmcpy.src.llm_integration import ClaudeSDKProvider
@@ -930,6 +930,7 @@ class AnthropicProvider(LLMProvider):
930
930
  api_key: str | None = None,
931
931
  base_url: str = "https://api.anthropic.com",
932
932
  mcp_url: str | None = None,
933
+ auth: dict[str, Any] | None = None,
933
934
  ):
934
935
  self.model = model
935
936
  # Use config system for API key
@@ -940,11 +941,10 @@ class AnthropicProvider(LLMProvider):
940
941
  # Use MCP_URL and auth from default profile if not provided
941
942
  if mcp_url is None:
942
943
  mcp_url = config.get_mcp_url()
943
- # Get auth from default MCP server
944
- auth = None
945
- default_mcp = config.get_default_mcp_server()
946
- if default_mcp and default_mcp.auth:
947
- auth = default_mcp.auth.to_dict()
944
+ if auth is None:
945
+ default_mcp = config.get_default_mcp_server()
946
+ if default_mcp and default_mcp.auth:
947
+ auth = default_mcp.auth.to_dict()
948
948
  self.tool_discovery = ToolDiscoveryService(mcp_url, auth=auth)
949
949
 
950
950
  async def initialize(self):
@@ -138,21 +138,38 @@ class TestStorage:
138
138
  # a manual `alembic upgrade` on existing DB files.
139
139
  self._apply_column_migrations()
140
140
 
141
+ @staticmethod
142
+ def _sqlite_default_literal(column) -> str | None:
143
+ """SQLite DDL DEFAULT literal for a mapped column's scalar default.
144
+
145
+ Only plain scalar defaults are rendered (bool/int/float/str) — callables
146
+ and server-side expressions return None so the column is added without
147
+ a DEFAULT (NULL for existing rows, Python-side default for new ones).
148
+ """
149
+ default = column.default
150
+ if default is None or not getattr(default, "is_scalar", False):
151
+ return None
152
+ value = default.arg
153
+ if isinstance(value, bool):
154
+ return str(int(value))
155
+ if isinstance(value, (int, float)):
156
+ return str(value)
157
+ if isinstance(value, str):
158
+ escaped = value.replace("'", "''")
159
+ return f"'{escaped}'"
160
+ return None
161
+
141
162
  def _apply_column_migrations(self) -> None:
142
163
  """Idempotently add columns that were introduced after the initial schema.
143
164
 
144
165
  create_all() creates missing *tables* but never alters existing ones, so
145
166
  users who created their DB before a new column was added will hit
146
- OperationalError on INSERT. This method uses PRAGMA table_info to detect
147
- missing columns and issues ALTER TABLE ADD COLUMN for each one.
167
+ OperationalError on SELECT/INSERT. Missing columns are derived from the
168
+ ORM metadata (every mapped column not present in the live table) rather
169
+ than a hand-maintained list — the previous list drifted from the models
170
+ more than once (e.g. ``manual_false_positive`` was missed, breaking
171
+ ``GET /api/results/run/{id}`` on older DB files with a 500).
148
172
  """
149
- # (table, column, DDL type)
150
- migrations = [
151
- ("question_results", "tool_call_counts", "JSON"),
152
- ("question_results", "false_positive_rate", "FLOAT DEFAULT 0.0"),
153
- ("test_runs", "total_cost", "FLOAT DEFAULT 0.0"),
154
- ("test_runs", "heartbeat_at", "VARCHAR"),
155
- ]
156
173
  # (index name, table, columns) — indexes added after the initial
157
174
  # schema; create_all skips existing tables so these need explicit DDL
158
175
  index_migrations = [
@@ -172,14 +189,23 @@ class TestStorage:
172
189
  if is_sqlite:
173
190
  # PRAGMA is SQLite-only; non-SQLite backends are expected
174
191
  # to be managed with `alembic upgrade head`.
175
- for table, column, col_type in migrations:
176
- result = conn.execute(text(f"PRAGMA table_info({table})"))
192
+ for table in Base.metadata.sorted_tables:
193
+ result = conn.execute(text(f"PRAGMA table_info({table.name})"))
177
194
  existing = {row[1] for row in result}
178
- if column not in existing:
179
- conn.execute(text(f"ALTER TABLE {table} ADD COLUMN {column} {col_type}"))
180
- for index_name, table, columns in index_migrations:
195
+ if not existing:
196
+ continue # create_all just made it; nothing to alter
197
+ for column in table.columns:
198
+ if column.name in existing:
199
+ continue
200
+ ddl_type = column.type.compile(self._engine.dialect)
201
+ ddl = f"ALTER TABLE {table.name} ADD COLUMN {column.name} {ddl_type}"
202
+ default = self._sqlite_default_literal(column)
203
+ if default is not None:
204
+ ddl += f" DEFAULT {default}"
205
+ conn.execute(text(ddl))
206
+ for index_name, table_name, columns in index_migrations:
181
207
  conn.execute(
182
- text(f"CREATE INDEX IF NOT EXISTS {index_name} ON {table} ({columns})")
208
+ text(f"CREATE INDEX IF NOT EXISTS {index_name} ON {table_name} ({columns})")
183
209
  )
184
210
  conn.commit()
185
211
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: testmcpy
3
- Version: 0.9.0
3
+ Version: 0.9.2
4
4
  Summary: A comprehensive testing framework for validating LLM tool calling capabilities with MCP services
5
5
  Author: Amin Ghadersohi
6
6
  License-Expression: Apache-2.0
@@ -351,6 +351,13 @@ profiles:
351
351
 
352
352
  The setup command is **idempotent** — safe to run multiple times. Use `--force` to overwrite existing files.
353
353
 
354
+ **`TESTMCPY_CHAT_OAUTH_LOGIN`** (default `true`): when a chat message hits an
355
+ OAuth (`oauth_auto_discover`) MCP profile with no cached token, the server opens
356
+ the interactive browser OAuth flow and retries. This assumes a browser is
357
+ available on the machine running the server — in headless deployments set
358
+ `TESTMCPY_CHAT_OAUTH_LOGIN=false` so the request fails fast with a clear error
359
+ instead of blocking on a login that can never complete.
360
+
354
361
  ### 2. Explore Your MCP Service
355
362
 
356
363
  ```bash
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes