attune-ai 2.0.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 (457) hide show
  1. attune/__init__.py +358 -0
  2. attune/adaptive/__init__.py +13 -0
  3. attune/adaptive/task_complexity.py +127 -0
  4. attune/agent_monitoring.py +414 -0
  5. attune/cache/__init__.py +117 -0
  6. attune/cache/base.py +166 -0
  7. attune/cache/dependency_manager.py +256 -0
  8. attune/cache/hash_only.py +251 -0
  9. attune/cache/hybrid.py +457 -0
  10. attune/cache/storage.py +285 -0
  11. attune/cache_monitor.py +356 -0
  12. attune/cache_stats.py +298 -0
  13. attune/cli/__init__.py +152 -0
  14. attune/cli/__main__.py +12 -0
  15. attune/cli/commands/__init__.py +1 -0
  16. attune/cli/commands/batch.py +264 -0
  17. attune/cli/commands/cache.py +248 -0
  18. attune/cli/commands/help.py +331 -0
  19. attune/cli/commands/info.py +140 -0
  20. attune/cli/commands/inspect.py +436 -0
  21. attune/cli/commands/inspection.py +57 -0
  22. attune/cli/commands/memory.py +48 -0
  23. attune/cli/commands/metrics.py +92 -0
  24. attune/cli/commands/orchestrate.py +184 -0
  25. attune/cli/commands/patterns.py +207 -0
  26. attune/cli/commands/profiling.py +202 -0
  27. attune/cli/commands/provider.py +98 -0
  28. attune/cli/commands/routing.py +285 -0
  29. attune/cli/commands/setup.py +96 -0
  30. attune/cli/commands/status.py +235 -0
  31. attune/cli/commands/sync.py +166 -0
  32. attune/cli/commands/tier.py +121 -0
  33. attune/cli/commands/utilities.py +114 -0
  34. attune/cli/commands/workflow.py +579 -0
  35. attune/cli/core.py +32 -0
  36. attune/cli/parsers/__init__.py +68 -0
  37. attune/cli/parsers/batch.py +118 -0
  38. attune/cli/parsers/cache.py +65 -0
  39. attune/cli/parsers/help.py +41 -0
  40. attune/cli/parsers/info.py +26 -0
  41. attune/cli/parsers/inspect.py +66 -0
  42. attune/cli/parsers/metrics.py +42 -0
  43. attune/cli/parsers/orchestrate.py +61 -0
  44. attune/cli/parsers/patterns.py +54 -0
  45. attune/cli/parsers/provider.py +40 -0
  46. attune/cli/parsers/routing.py +110 -0
  47. attune/cli/parsers/setup.py +42 -0
  48. attune/cli/parsers/status.py +47 -0
  49. attune/cli/parsers/sync.py +31 -0
  50. attune/cli/parsers/tier.py +33 -0
  51. attune/cli/parsers/workflow.py +77 -0
  52. attune/cli/utils/__init__.py +1 -0
  53. attune/cli/utils/data.py +242 -0
  54. attune/cli/utils/helpers.py +68 -0
  55. attune/cli_legacy.py +3957 -0
  56. attune/cli_minimal.py +1159 -0
  57. attune/cli_router.py +437 -0
  58. attune/cli_unified.py +814 -0
  59. attune/config/__init__.py +66 -0
  60. attune/config/xml_config.py +286 -0
  61. attune/config.py +545 -0
  62. attune/coordination.py +870 -0
  63. attune/core.py +1511 -0
  64. attune/core_modules/__init__.py +15 -0
  65. attune/cost_tracker.py +626 -0
  66. attune/dashboard/__init__.py +41 -0
  67. attune/dashboard/app.py +512 -0
  68. attune/dashboard/simple_server.py +435 -0
  69. attune/dashboard/standalone_server.py +547 -0
  70. attune/discovery.py +306 -0
  71. attune/emergence.py +306 -0
  72. attune/exceptions.py +123 -0
  73. attune/feedback_loops.py +373 -0
  74. attune/hot_reload/README.md +473 -0
  75. attune/hot_reload/__init__.py +62 -0
  76. attune/hot_reload/config.py +83 -0
  77. attune/hot_reload/integration.py +229 -0
  78. attune/hot_reload/reloader.py +298 -0
  79. attune/hot_reload/watcher.py +183 -0
  80. attune/hot_reload/websocket.py +177 -0
  81. attune/levels.py +577 -0
  82. attune/leverage_points.py +441 -0
  83. attune/logging_config.py +261 -0
  84. attune/mcp/__init__.py +10 -0
  85. attune/mcp/server.py +506 -0
  86. attune/memory/__init__.py +237 -0
  87. attune/memory/claude_memory.py +469 -0
  88. attune/memory/config.py +224 -0
  89. attune/memory/control_panel.py +1290 -0
  90. attune/memory/control_panel_support.py +145 -0
  91. attune/memory/cross_session.py +845 -0
  92. attune/memory/edges.py +179 -0
  93. attune/memory/encryption.py +159 -0
  94. attune/memory/file_session.py +770 -0
  95. attune/memory/graph.py +570 -0
  96. attune/memory/long_term.py +913 -0
  97. attune/memory/long_term_types.py +99 -0
  98. attune/memory/mixins/__init__.py +25 -0
  99. attune/memory/mixins/backend_init_mixin.py +249 -0
  100. attune/memory/mixins/capabilities_mixin.py +208 -0
  101. attune/memory/mixins/handoff_mixin.py +208 -0
  102. attune/memory/mixins/lifecycle_mixin.py +49 -0
  103. attune/memory/mixins/long_term_mixin.py +352 -0
  104. attune/memory/mixins/promotion_mixin.py +109 -0
  105. attune/memory/mixins/short_term_mixin.py +182 -0
  106. attune/memory/nodes.py +179 -0
  107. attune/memory/redis_bootstrap.py +540 -0
  108. attune/memory/security/__init__.py +31 -0
  109. attune/memory/security/audit_logger.py +932 -0
  110. attune/memory/security/pii_scrubber.py +640 -0
  111. attune/memory/security/secrets_detector.py +678 -0
  112. attune/memory/short_term.py +2192 -0
  113. attune/memory/simple_storage.py +302 -0
  114. attune/memory/storage/__init__.py +15 -0
  115. attune/memory/storage_backend.py +167 -0
  116. attune/memory/summary_index.py +583 -0
  117. attune/memory/types.py +446 -0
  118. attune/memory/unified.py +182 -0
  119. attune/meta_workflows/__init__.py +74 -0
  120. attune/meta_workflows/agent_creator.py +248 -0
  121. attune/meta_workflows/builtin_templates.py +567 -0
  122. attune/meta_workflows/cli_commands/__init__.py +56 -0
  123. attune/meta_workflows/cli_commands/agent_commands.py +321 -0
  124. attune/meta_workflows/cli_commands/analytics_commands.py +442 -0
  125. attune/meta_workflows/cli_commands/config_commands.py +232 -0
  126. attune/meta_workflows/cli_commands/memory_commands.py +182 -0
  127. attune/meta_workflows/cli_commands/template_commands.py +354 -0
  128. attune/meta_workflows/cli_commands/workflow_commands.py +382 -0
  129. attune/meta_workflows/cli_meta_workflows.py +59 -0
  130. attune/meta_workflows/form_engine.py +292 -0
  131. attune/meta_workflows/intent_detector.py +409 -0
  132. attune/meta_workflows/models.py +569 -0
  133. attune/meta_workflows/pattern_learner.py +738 -0
  134. attune/meta_workflows/plan_generator.py +384 -0
  135. attune/meta_workflows/session_context.py +397 -0
  136. attune/meta_workflows/template_registry.py +229 -0
  137. attune/meta_workflows/workflow.py +984 -0
  138. attune/metrics/__init__.py +12 -0
  139. attune/metrics/collector.py +31 -0
  140. attune/metrics/prompt_metrics.py +194 -0
  141. attune/models/__init__.py +172 -0
  142. attune/models/__main__.py +13 -0
  143. attune/models/adaptive_routing.py +437 -0
  144. attune/models/auth_cli.py +444 -0
  145. attune/models/auth_strategy.py +450 -0
  146. attune/models/cli.py +655 -0
  147. attune/models/empathy_executor.py +354 -0
  148. attune/models/executor.py +257 -0
  149. attune/models/fallback.py +762 -0
  150. attune/models/provider_config.py +282 -0
  151. attune/models/registry.py +472 -0
  152. attune/models/tasks.py +359 -0
  153. attune/models/telemetry/__init__.py +71 -0
  154. attune/models/telemetry/analytics.py +594 -0
  155. attune/models/telemetry/backend.py +196 -0
  156. attune/models/telemetry/data_models.py +431 -0
  157. attune/models/telemetry/storage.py +489 -0
  158. attune/models/token_estimator.py +420 -0
  159. attune/models/validation.py +280 -0
  160. attune/monitoring/__init__.py +52 -0
  161. attune/monitoring/alerts.py +946 -0
  162. attune/monitoring/alerts_cli.py +448 -0
  163. attune/monitoring/multi_backend.py +271 -0
  164. attune/monitoring/otel_backend.py +362 -0
  165. attune/optimization/__init__.py +19 -0
  166. attune/optimization/context_optimizer.py +272 -0
  167. attune/orchestration/__init__.py +67 -0
  168. attune/orchestration/agent_templates.py +707 -0
  169. attune/orchestration/config_store.py +499 -0
  170. attune/orchestration/execution_strategies.py +2111 -0
  171. attune/orchestration/meta_orchestrator.py +1168 -0
  172. attune/orchestration/pattern_learner.py +696 -0
  173. attune/orchestration/real_tools.py +931 -0
  174. attune/pattern_cache.py +187 -0
  175. attune/pattern_library.py +542 -0
  176. attune/patterns/debugging/all_patterns.json +81 -0
  177. attune/patterns/debugging/workflow_20260107_1770825e.json +77 -0
  178. attune/patterns/refactoring_memory.json +89 -0
  179. attune/persistence.py +564 -0
  180. attune/platform_utils.py +265 -0
  181. attune/plugins/__init__.py +28 -0
  182. attune/plugins/base.py +361 -0
  183. attune/plugins/registry.py +268 -0
  184. attune/project_index/__init__.py +32 -0
  185. attune/project_index/cli.py +335 -0
  186. attune/project_index/index.py +667 -0
  187. attune/project_index/models.py +504 -0
  188. attune/project_index/reports.py +474 -0
  189. attune/project_index/scanner.py +777 -0
  190. attune/project_index/scanner_parallel.py +291 -0
  191. attune/prompts/__init__.py +61 -0
  192. attune/prompts/config.py +77 -0
  193. attune/prompts/context.py +177 -0
  194. attune/prompts/parser.py +285 -0
  195. attune/prompts/registry.py +313 -0
  196. attune/prompts/templates.py +208 -0
  197. attune/redis_config.py +302 -0
  198. attune/redis_memory.py +799 -0
  199. attune/resilience/__init__.py +56 -0
  200. attune/resilience/circuit_breaker.py +256 -0
  201. attune/resilience/fallback.py +179 -0
  202. attune/resilience/health.py +300 -0
  203. attune/resilience/retry.py +209 -0
  204. attune/resilience/timeout.py +135 -0
  205. attune/routing/__init__.py +43 -0
  206. attune/routing/chain_executor.py +433 -0
  207. attune/routing/classifier.py +217 -0
  208. attune/routing/smart_router.py +234 -0
  209. attune/routing/workflow_registry.py +343 -0
  210. attune/scaffolding/README.md +589 -0
  211. attune/scaffolding/__init__.py +35 -0
  212. attune/scaffolding/__main__.py +14 -0
  213. attune/scaffolding/cli.py +240 -0
  214. attune/scaffolding/templates/base_wizard.py.jinja2 +121 -0
  215. attune/scaffolding/templates/coach_wizard.py.jinja2 +321 -0
  216. attune/scaffolding/templates/domain_wizard.py.jinja2 +408 -0
  217. attune/scaffolding/templates/linear_flow_wizard.py.jinja2 +203 -0
  218. attune/socratic/__init__.py +256 -0
  219. attune/socratic/ab_testing.py +958 -0
  220. attune/socratic/blueprint.py +533 -0
  221. attune/socratic/cli.py +703 -0
  222. attune/socratic/collaboration.py +1114 -0
  223. attune/socratic/domain_templates.py +924 -0
  224. attune/socratic/embeddings.py +738 -0
  225. attune/socratic/engine.py +794 -0
  226. attune/socratic/explainer.py +682 -0
  227. attune/socratic/feedback.py +772 -0
  228. attune/socratic/forms.py +629 -0
  229. attune/socratic/generator.py +732 -0
  230. attune/socratic/llm_analyzer.py +637 -0
  231. attune/socratic/mcp_server.py +702 -0
  232. attune/socratic/session.py +312 -0
  233. attune/socratic/storage.py +667 -0
  234. attune/socratic/success.py +730 -0
  235. attune/socratic/visual_editor.py +860 -0
  236. attune/socratic/web_ui.py +958 -0
  237. attune/telemetry/__init__.py +39 -0
  238. attune/telemetry/agent_coordination.py +475 -0
  239. attune/telemetry/agent_tracking.py +367 -0
  240. attune/telemetry/approval_gates.py +545 -0
  241. attune/telemetry/cli.py +1231 -0
  242. attune/telemetry/commands/__init__.py +14 -0
  243. attune/telemetry/commands/dashboard_commands.py +696 -0
  244. attune/telemetry/event_streaming.py +409 -0
  245. attune/telemetry/feedback_loop.py +567 -0
  246. attune/telemetry/usage_tracker.py +591 -0
  247. attune/templates.py +754 -0
  248. attune/test_generator/__init__.py +38 -0
  249. attune/test_generator/__main__.py +14 -0
  250. attune/test_generator/cli.py +234 -0
  251. attune/test_generator/generator.py +355 -0
  252. attune/test_generator/risk_analyzer.py +216 -0
  253. attune/test_generator/templates/unit_test.py.jinja2 +272 -0
  254. attune/tier_recommender.py +384 -0
  255. attune/tools.py +183 -0
  256. attune/trust/__init__.py +28 -0
  257. attune/trust/circuit_breaker.py +579 -0
  258. attune/trust_building.py +527 -0
  259. attune/validation/__init__.py +19 -0
  260. attune/validation/xml_validator.py +281 -0
  261. attune/vscode_bridge.py +173 -0
  262. attune/workflow_commands.py +780 -0
  263. attune/workflow_patterns/__init__.py +33 -0
  264. attune/workflow_patterns/behavior.py +249 -0
  265. attune/workflow_patterns/core.py +76 -0
  266. attune/workflow_patterns/output.py +99 -0
  267. attune/workflow_patterns/registry.py +255 -0
  268. attune/workflow_patterns/structural.py +288 -0
  269. attune/workflows/__init__.py +539 -0
  270. attune/workflows/autonomous_test_gen.py +1268 -0
  271. attune/workflows/base.py +2667 -0
  272. attune/workflows/batch_processing.py +342 -0
  273. attune/workflows/bug_predict.py +1084 -0
  274. attune/workflows/builder.py +273 -0
  275. attune/workflows/caching.py +253 -0
  276. attune/workflows/code_review.py +1048 -0
  277. attune/workflows/code_review_adapters.py +312 -0
  278. attune/workflows/code_review_pipeline.py +722 -0
  279. attune/workflows/config.py +645 -0
  280. attune/workflows/dependency_check.py +644 -0
  281. attune/workflows/document_gen/__init__.py +25 -0
  282. attune/workflows/document_gen/config.py +30 -0
  283. attune/workflows/document_gen/report_formatter.py +162 -0
  284. attune/workflows/document_gen/workflow.py +1426 -0
  285. attune/workflows/document_manager.py +216 -0
  286. attune/workflows/document_manager_README.md +134 -0
  287. attune/workflows/documentation_orchestrator.py +1205 -0
  288. attune/workflows/history.py +510 -0
  289. attune/workflows/keyboard_shortcuts/__init__.py +39 -0
  290. attune/workflows/keyboard_shortcuts/generators.py +391 -0
  291. attune/workflows/keyboard_shortcuts/parsers.py +416 -0
  292. attune/workflows/keyboard_shortcuts/prompts.py +295 -0
  293. attune/workflows/keyboard_shortcuts/schema.py +193 -0
  294. attune/workflows/keyboard_shortcuts/workflow.py +509 -0
  295. attune/workflows/llm_base.py +363 -0
  296. attune/workflows/manage_docs.py +87 -0
  297. attune/workflows/manage_docs_README.md +134 -0
  298. attune/workflows/manage_documentation.py +821 -0
  299. attune/workflows/new_sample_workflow1.py +149 -0
  300. attune/workflows/new_sample_workflow1_README.md +150 -0
  301. attune/workflows/orchestrated_health_check.py +849 -0
  302. attune/workflows/orchestrated_release_prep.py +600 -0
  303. attune/workflows/output.py +413 -0
  304. attune/workflows/perf_audit.py +863 -0
  305. attune/workflows/pr_review.py +762 -0
  306. attune/workflows/progress.py +785 -0
  307. attune/workflows/progress_server.py +322 -0
  308. attune/workflows/progressive/README 2.md +454 -0
  309. attune/workflows/progressive/README.md +454 -0
  310. attune/workflows/progressive/__init__.py +82 -0
  311. attune/workflows/progressive/cli.py +219 -0
  312. attune/workflows/progressive/core.py +488 -0
  313. attune/workflows/progressive/orchestrator.py +723 -0
  314. attune/workflows/progressive/reports.py +520 -0
  315. attune/workflows/progressive/telemetry.py +274 -0
  316. attune/workflows/progressive/test_gen.py +495 -0
  317. attune/workflows/progressive/workflow.py +589 -0
  318. attune/workflows/refactor_plan.py +694 -0
  319. attune/workflows/release_prep.py +895 -0
  320. attune/workflows/release_prep_crew.py +969 -0
  321. attune/workflows/research_synthesis.py +404 -0
  322. attune/workflows/routing.py +168 -0
  323. attune/workflows/secure_release.py +593 -0
  324. attune/workflows/security_adapters.py +297 -0
  325. attune/workflows/security_audit.py +1329 -0
  326. attune/workflows/security_audit_phase3.py +355 -0
  327. attune/workflows/seo_optimization.py +633 -0
  328. attune/workflows/step_config.py +234 -0
  329. attune/workflows/telemetry_mixin.py +269 -0
  330. attune/workflows/test5.py +125 -0
  331. attune/workflows/test5_README.md +158 -0
  332. attune/workflows/test_coverage_boost_crew.py +849 -0
  333. attune/workflows/test_gen/__init__.py +52 -0
  334. attune/workflows/test_gen/ast_analyzer.py +249 -0
  335. attune/workflows/test_gen/config.py +88 -0
  336. attune/workflows/test_gen/data_models.py +38 -0
  337. attune/workflows/test_gen/report_formatter.py +289 -0
  338. attune/workflows/test_gen/test_templates.py +381 -0
  339. attune/workflows/test_gen/workflow.py +655 -0
  340. attune/workflows/test_gen.py +54 -0
  341. attune/workflows/test_gen_behavioral.py +477 -0
  342. attune/workflows/test_gen_parallel.py +341 -0
  343. attune/workflows/test_lifecycle.py +526 -0
  344. attune/workflows/test_maintenance.py +627 -0
  345. attune/workflows/test_maintenance_cli.py +590 -0
  346. attune/workflows/test_maintenance_crew.py +840 -0
  347. attune/workflows/test_runner.py +622 -0
  348. attune/workflows/tier_tracking.py +531 -0
  349. attune/workflows/xml_enhanced_crew.py +285 -0
  350. attune_ai-2.0.0.dist-info/METADATA +1026 -0
  351. attune_ai-2.0.0.dist-info/RECORD +457 -0
  352. attune_ai-2.0.0.dist-info/WHEEL +5 -0
  353. attune_ai-2.0.0.dist-info/entry_points.txt +26 -0
  354. attune_ai-2.0.0.dist-info/licenses/LICENSE +201 -0
  355. attune_ai-2.0.0.dist-info/licenses/LICENSE_CHANGE_ANNOUNCEMENT.md +101 -0
  356. attune_ai-2.0.0.dist-info/top_level.txt +5 -0
  357. attune_healthcare/__init__.py +13 -0
  358. attune_healthcare/monitors/__init__.py +9 -0
  359. attune_healthcare/monitors/clinical_protocol_monitor.py +315 -0
  360. attune_healthcare/monitors/monitoring/__init__.py +44 -0
  361. attune_healthcare/monitors/monitoring/protocol_checker.py +300 -0
  362. attune_healthcare/monitors/monitoring/protocol_loader.py +214 -0
  363. attune_healthcare/monitors/monitoring/sensor_parsers.py +306 -0
  364. attune_healthcare/monitors/monitoring/trajectory_analyzer.py +389 -0
  365. attune_llm/README.md +553 -0
  366. attune_llm/__init__.py +28 -0
  367. attune_llm/agent_factory/__init__.py +53 -0
  368. attune_llm/agent_factory/adapters/__init__.py +85 -0
  369. attune_llm/agent_factory/adapters/autogen_adapter.py +312 -0
  370. attune_llm/agent_factory/adapters/crewai_adapter.py +483 -0
  371. attune_llm/agent_factory/adapters/haystack_adapter.py +298 -0
  372. attune_llm/agent_factory/adapters/langchain_adapter.py +362 -0
  373. attune_llm/agent_factory/adapters/langgraph_adapter.py +333 -0
  374. attune_llm/agent_factory/adapters/native.py +228 -0
  375. attune_llm/agent_factory/adapters/wizard_adapter.py +423 -0
  376. attune_llm/agent_factory/base.py +305 -0
  377. attune_llm/agent_factory/crews/__init__.py +67 -0
  378. attune_llm/agent_factory/crews/code_review.py +1113 -0
  379. attune_llm/agent_factory/crews/health_check.py +1262 -0
  380. attune_llm/agent_factory/crews/refactoring.py +1128 -0
  381. attune_llm/agent_factory/crews/security_audit.py +1018 -0
  382. attune_llm/agent_factory/decorators.py +287 -0
  383. attune_llm/agent_factory/factory.py +558 -0
  384. attune_llm/agent_factory/framework.py +193 -0
  385. attune_llm/agent_factory/memory_integration.py +328 -0
  386. attune_llm/agent_factory/resilient.py +320 -0
  387. attune_llm/agents_md/__init__.py +22 -0
  388. attune_llm/agents_md/loader.py +218 -0
  389. attune_llm/agents_md/parser.py +271 -0
  390. attune_llm/agents_md/registry.py +307 -0
  391. attune_llm/claude_memory.py +466 -0
  392. attune_llm/cli/__init__.py +8 -0
  393. attune_llm/cli/sync_claude.py +487 -0
  394. attune_llm/code_health.py +1313 -0
  395. attune_llm/commands/__init__.py +51 -0
  396. attune_llm/commands/context.py +375 -0
  397. attune_llm/commands/loader.py +301 -0
  398. attune_llm/commands/models.py +231 -0
  399. attune_llm/commands/parser.py +371 -0
  400. attune_llm/commands/registry.py +429 -0
  401. attune_llm/config/__init__.py +29 -0
  402. attune_llm/config/unified.py +291 -0
  403. attune_llm/context/__init__.py +22 -0
  404. attune_llm/context/compaction.py +455 -0
  405. attune_llm/context/manager.py +434 -0
  406. attune_llm/contextual_patterns.py +361 -0
  407. attune_llm/core.py +907 -0
  408. attune_llm/git_pattern_extractor.py +435 -0
  409. attune_llm/hooks/__init__.py +24 -0
  410. attune_llm/hooks/config.py +306 -0
  411. attune_llm/hooks/executor.py +289 -0
  412. attune_llm/hooks/registry.py +302 -0
  413. attune_llm/hooks/scripts/__init__.py +39 -0
  414. attune_llm/hooks/scripts/evaluate_session.py +201 -0
  415. attune_llm/hooks/scripts/first_time_init.py +285 -0
  416. attune_llm/hooks/scripts/pre_compact.py +207 -0
  417. attune_llm/hooks/scripts/session_end.py +183 -0
  418. attune_llm/hooks/scripts/session_start.py +163 -0
  419. attune_llm/hooks/scripts/suggest_compact.py +225 -0
  420. attune_llm/learning/__init__.py +30 -0
  421. attune_llm/learning/evaluator.py +438 -0
  422. attune_llm/learning/extractor.py +514 -0
  423. attune_llm/learning/storage.py +560 -0
  424. attune_llm/levels.py +227 -0
  425. attune_llm/pattern_confidence.py +414 -0
  426. attune_llm/pattern_resolver.py +272 -0
  427. attune_llm/pattern_summary.py +350 -0
  428. attune_llm/providers.py +967 -0
  429. attune_llm/routing/__init__.py +32 -0
  430. attune_llm/routing/model_router.py +362 -0
  431. attune_llm/security/IMPLEMENTATION_SUMMARY.md +413 -0
  432. attune_llm/security/PHASE2_COMPLETE.md +384 -0
  433. attune_llm/security/PHASE2_SECRETS_DETECTOR_COMPLETE.md +271 -0
  434. attune_llm/security/QUICK_REFERENCE.md +316 -0
  435. attune_llm/security/README.md +262 -0
  436. attune_llm/security/__init__.py +62 -0
  437. attune_llm/security/audit_logger.py +929 -0
  438. attune_llm/security/audit_logger_example.py +152 -0
  439. attune_llm/security/pii_scrubber.py +640 -0
  440. attune_llm/security/secrets_detector.py +678 -0
  441. attune_llm/security/secrets_detector_example.py +304 -0
  442. attune_llm/security/secure_memdocs.py +1192 -0
  443. attune_llm/security/secure_memdocs_example.py +278 -0
  444. attune_llm/session_status.py +745 -0
  445. attune_llm/state.py +246 -0
  446. attune_llm/utils/__init__.py +5 -0
  447. attune_llm/utils/tokens.py +349 -0
  448. attune_software/SOFTWARE_PLUGIN_README.md +57 -0
  449. attune_software/__init__.py +13 -0
  450. attune_software/cli/__init__.py +120 -0
  451. attune_software/cli/inspect.py +362 -0
  452. attune_software/cli.py +574 -0
  453. attune_software/plugin.py +188 -0
  454. workflow_scaffolding/__init__.py +11 -0
  455. workflow_scaffolding/__main__.py +12 -0
  456. workflow_scaffolding/cli.py +206 -0
  457. workflow_scaffolding/generator.py +265 -0
@@ -0,0 +1,702 @@
1
+ """MCP Server for Socratic Agent Generation System.
2
+
3
+ Exposes the Socratic workflow builder as MCP tools for Claude Desktop/Code.
4
+
5
+ Usage:
6
+ python -m attune.socratic.mcp_server
7
+
8
+ Or add to Claude Desktop config:
9
+ {
10
+ "mcpServers": {
11
+ "socratic": {
12
+ "command": "python",
13
+ "args": ["-m", "attune.socratic.mcp_server"],
14
+ "env": {
15
+ "ANTHROPIC_API_KEY": "your-key"
16
+ }
17
+ }
18
+ }
19
+ }
20
+
21
+ Copyright 2026 Smart-AI-Memory
22
+ Licensed under Fair Source License 0.9
23
+ """
24
+
25
+ import asyncio
26
+ import json
27
+ import logging
28
+ import os
29
+ import sys
30
+ from typing import Any
31
+
32
+ logger = logging.getLogger(__name__)
33
+
34
+ # MCP Protocol Types
35
+ MCP_VERSION = "2024-11-05"
36
+
37
+ # Tool definitions for the Socratic system
38
+ SOCRATIC_TOOLS = [
39
+ {
40
+ "name": "socratic_start_session",
41
+ "description": "Start a new Socratic workflow builder session. Returns a session ID and initial state.",
42
+ "inputSchema": {
43
+ "type": "object",
44
+ "properties": {
45
+ "goal": {
46
+ "type": "string",
47
+ "description": "Optional initial goal. If not provided, session starts in AWAITING_GOAL state.",
48
+ }
49
+ },
50
+ "required": [],
51
+ },
52
+ },
53
+ {
54
+ "name": "socratic_set_goal",
55
+ "description": "Set or update the goal for a session. Triggers goal analysis and domain detection.",
56
+ "inputSchema": {
57
+ "type": "object",
58
+ "properties": {
59
+ "session_id": {"type": "string", "description": "The session ID to update"},
60
+ "goal": {"type": "string", "description": "The user's goal in free-form text"},
61
+ },
62
+ "required": ["session_id", "goal"],
63
+ },
64
+ },
65
+ {
66
+ "name": "socratic_get_questions",
67
+ "description": "Get the next set of clarifying questions for a session.",
68
+ "inputSchema": {
69
+ "type": "object",
70
+ "properties": {"session_id": {"type": "string", "description": "The session ID"}},
71
+ "required": ["session_id"],
72
+ },
73
+ },
74
+ {
75
+ "name": "socratic_submit_answers",
76
+ "description": "Submit answers to clarifying questions.",
77
+ "inputSchema": {
78
+ "type": "object",
79
+ "properties": {
80
+ "session_id": {"type": "string", "description": "The session ID"},
81
+ "answers": {
82
+ "type": "object",
83
+ "description": "Dictionary of field_id -> answer value",
84
+ },
85
+ },
86
+ "required": ["session_id", "answers"],
87
+ },
88
+ },
89
+ {
90
+ "name": "socratic_generate_workflow",
91
+ "description": "Generate the workflow once all questions are answered. Returns agent blueprints and success criteria.",
92
+ "inputSchema": {
93
+ "type": "object",
94
+ "properties": {"session_id": {"type": "string", "description": "The session ID"}},
95
+ "required": ["session_id"],
96
+ },
97
+ },
98
+ {
99
+ "name": "socratic_list_sessions",
100
+ "description": "List all saved Socratic sessions.",
101
+ "inputSchema": {
102
+ "type": "object",
103
+ "properties": {
104
+ "status_filter": {
105
+ "type": "string",
106
+ "enum": ["all", "active", "completed"],
107
+ "description": "Filter sessions by status",
108
+ }
109
+ },
110
+ "required": [],
111
+ },
112
+ },
113
+ {
114
+ "name": "socratic_get_session",
115
+ "description": "Get details of a specific session.",
116
+ "inputSchema": {
117
+ "type": "object",
118
+ "properties": {
119
+ "session_id": {"type": "string", "description": "The session ID to retrieve"}
120
+ },
121
+ "required": ["session_id"],
122
+ },
123
+ },
124
+ {
125
+ "name": "socratic_list_blueprints",
126
+ "description": "List all saved workflow blueprints.",
127
+ "inputSchema": {
128
+ "type": "object",
129
+ "properties": {
130
+ "domain_filter": {
131
+ "type": "string",
132
+ "description": "Optional domain to filter by (e.g., 'code_review', 'security')",
133
+ }
134
+ },
135
+ "required": [],
136
+ },
137
+ },
138
+ {
139
+ "name": "socratic_analyze_goal",
140
+ "description": "Analyze a goal using LLM to detect domains, requirements, and ambiguities without starting a full session.",
141
+ "inputSchema": {
142
+ "type": "object",
143
+ "properties": {"goal": {"type": "string", "description": "The goal to analyze"}},
144
+ "required": ["goal"],
145
+ },
146
+ },
147
+ {
148
+ "name": "socratic_recommend_agents",
149
+ "description": "Get agent recommendations based on requirements and historical success data.",
150
+ "inputSchema": {
151
+ "type": "object",
152
+ "properties": {
153
+ "domains": {
154
+ "type": "array",
155
+ "items": {"type": "string"},
156
+ "description": "List of domains (e.g., ['code_review', 'security'])",
157
+ },
158
+ "languages": {
159
+ "type": "array",
160
+ "items": {"type": "string"},
161
+ "description": "Programming languages involved",
162
+ },
163
+ "quality_focus": {
164
+ "type": "array",
165
+ "items": {"type": "string"},
166
+ "description": "Quality focus areas (e.g., ['security', 'performance'])",
167
+ },
168
+ },
169
+ "required": ["domains"],
170
+ },
171
+ },
172
+ ]
173
+
174
+
175
+ class SocraticMCPServer:
176
+ """MCP Server exposing Socratic workflow builder tools."""
177
+
178
+ def __init__(self):
179
+ """Initialize the MCP server."""
180
+ self._sessions: dict[str, Any] = {}
181
+ self._builder = None
182
+ self._storage = None
183
+ self._llm_analyzer = None
184
+ self._feedback_loop = None
185
+ self._initialized = False
186
+
187
+ async def _ensure_initialized(self):
188
+ """Lazily initialize components."""
189
+ if self._initialized:
190
+ return
191
+
192
+ try:
193
+ from .engine import SocraticWorkflowBuilder
194
+ from .feedback import FeedbackLoop
195
+ from .llm_analyzer import LLMGoalAnalyzer
196
+ from .storage import JSONFileStorage, set_default_storage
197
+
198
+ # Initialize storage
199
+ self._storage = JSONFileStorage()
200
+ set_default_storage(self._storage)
201
+
202
+ # Initialize builder
203
+ self._builder = SocraticWorkflowBuilder()
204
+
205
+ # Initialize LLM analyzer if API key available
206
+ api_key = os.environ.get("ANTHROPIC_API_KEY")
207
+ if api_key:
208
+ self._llm_analyzer = LLMGoalAnalyzer(api_key=api_key)
209
+
210
+ # Initialize feedback loop (uses default storage path)
211
+ self._feedback_loop = FeedbackLoop()
212
+
213
+ self._initialized = True
214
+ logger.info("Socratic MCP Server initialized successfully")
215
+
216
+ except ImportError as e:
217
+ logger.warning(f"Some components not available: {e}")
218
+ self._initialized = True
219
+
220
+ async def handle_tool_call(self, name: str, arguments: dict[str, Any]) -> dict[str, Any]:
221
+ """Handle a tool call from Claude.
222
+
223
+ Args:
224
+ name: Tool name
225
+ arguments: Tool arguments
226
+
227
+ Returns:
228
+ Tool result as dictionary
229
+ """
230
+ await self._ensure_initialized()
231
+
232
+ handlers = {
233
+ "socratic_start_session": self._handle_start_session,
234
+ "socratic_set_goal": self._handle_set_goal,
235
+ "socratic_get_questions": self._handle_get_questions,
236
+ "socratic_submit_answers": self._handle_submit_answers,
237
+ "socratic_generate_workflow": self._handle_generate_workflow,
238
+ "socratic_list_sessions": self._handle_list_sessions,
239
+ "socratic_get_session": self._handle_get_session,
240
+ "socratic_list_blueprints": self._handle_list_blueprints,
241
+ "socratic_analyze_goal": self._handle_analyze_goal,
242
+ "socratic_recommend_agents": self._handle_recommend_agents,
243
+ }
244
+
245
+ handler = handlers.get(name)
246
+ if not handler:
247
+ return {"error": f"Unknown tool: {name}"}
248
+
249
+ try:
250
+ return await handler(arguments)
251
+ except Exception as e:
252
+ logger.exception(f"Error handling {name}")
253
+ return {"error": str(e)}
254
+
255
+ async def _handle_start_session(self, args: dict[str, Any]) -> dict[str, Any]:
256
+ """Start a new Socratic session."""
257
+ goal = args.get("goal")
258
+
259
+ session = self._builder.start_session(goal)
260
+ self._sessions[session.session_id] = session
261
+
262
+ # Persist session
263
+ if self._storage:
264
+ self._storage.save_session(session)
265
+
266
+ result = {
267
+ "session_id": session.session_id,
268
+ "state": session.state.value,
269
+ "message": "Session started successfully",
270
+ }
271
+
272
+ if goal:
273
+ result["goal"] = goal
274
+ # Get domain from goal_analysis if available
275
+ if session.goal_analysis:
276
+ result["detected_domain"] = session.goal_analysis.domain
277
+
278
+ return result
279
+
280
+ async def _handle_set_goal(self, args: dict[str, Any]) -> dict[str, Any]:
281
+ """Set goal for a session."""
282
+ session_id = args["session_id"]
283
+ goal = args["goal"]
284
+
285
+ session = self._get_session(session_id)
286
+ if not session:
287
+ return {"error": f"Session not found: {session_id}"}
288
+
289
+ # Use LLM analyzer if available
290
+ analysis = None
291
+ if self._llm_analyzer:
292
+ try:
293
+ analysis = await self._llm_analyzer.analyze_goal(goal)
294
+ except Exception as e:
295
+ logger.warning(f"LLM analysis failed, using fallback: {e}")
296
+
297
+ session = self._builder.set_goal(session, goal)
298
+ self._sessions[session_id] = session
299
+
300
+ if self._storage:
301
+ self._storage.save_session(session)
302
+
303
+ result = {
304
+ "session_id": session_id,
305
+ "state": session.state.value,
306
+ "goal": goal,
307
+ }
308
+ # Add domain from goal_analysis if available
309
+ if session.goal_analysis:
310
+ result["detected_domain"] = session.goal_analysis.domain
311
+
312
+ if analysis:
313
+ result["llm_analysis"] = {
314
+ "primary_domain": analysis.primary_domain,
315
+ "confidence": analysis.confidence,
316
+ "ambiguities": analysis.ambiguities,
317
+ "suggested_questions": analysis.suggested_questions[:3],
318
+ }
319
+
320
+ return result
321
+
322
+ async def _handle_get_questions(self, args: dict[str, Any]) -> dict[str, Any]:
323
+ """Get clarifying questions for a session."""
324
+ session_id = args["session_id"]
325
+
326
+ session = self._get_session(session_id)
327
+ if not session:
328
+ return {"error": f"Session not found: {session_id}"}
329
+
330
+ form = self._builder.get_next_questions(session)
331
+ if not form:
332
+ return {
333
+ "session_id": session_id,
334
+ "state": session.state.value,
335
+ "questions": [],
336
+ "message": "No more questions - ready to generate workflow",
337
+ }
338
+
339
+ # Convert form to JSON-serializable format
340
+ questions = []
341
+ for field in form.fields:
342
+ q = {
343
+ "field_id": field.id,
344
+ "type": field.field_type.value,
345
+ "label": field.label,
346
+ "required": field.validation.required if field.validation else False,
347
+ }
348
+ if field.options:
349
+ # Serialize FieldOption objects
350
+ q["options"] = [
351
+ {"value": opt.value, "label": opt.label, "description": opt.description}
352
+ for opt in field.options
353
+ ]
354
+ if field.placeholder:
355
+ q["placeholder"] = field.placeholder
356
+ if field.help_text:
357
+ q["help_text"] = field.help_text
358
+ if field.default is not None:
359
+ q["default"] = field.default
360
+ questions.append(q)
361
+
362
+ return {
363
+ "session_id": session_id,
364
+ "state": session.state.value,
365
+ "form_id": form.id,
366
+ "form_title": form.title,
367
+ "questions": questions,
368
+ }
369
+
370
+ async def _handle_submit_answers(self, args: dict[str, Any]) -> dict[str, Any]:
371
+ """Submit answers to questions."""
372
+ session_id = args["session_id"]
373
+ answers = args["answers"]
374
+
375
+ session = self._get_session(session_id)
376
+ if not session:
377
+ return {"error": f"Session not found: {session_id}"}
378
+
379
+ session = self._builder.submit_answers(session, answers)
380
+ self._sessions[session_id] = session
381
+
382
+ if self._storage:
383
+ self._storage.save_session(session)
384
+
385
+ ready = self._builder.is_ready_to_generate(session)
386
+
387
+ return {
388
+ "session_id": session_id,
389
+ "state": session.state.value,
390
+ "ready_to_generate": ready,
391
+ "message": "Ready to generate workflow" if ready else "More questions available",
392
+ }
393
+
394
+ async def _handle_generate_workflow(self, args: dict[str, Any]) -> dict[str, Any]:
395
+ """Generate workflow from session."""
396
+ session_id = args["session_id"]
397
+
398
+ session = self._get_session(session_id)
399
+ if not session:
400
+ return {"error": f"Session not found: {session_id}"}
401
+
402
+ if not self._builder.is_ready_to_generate(session):
403
+ return {"error": "Session not ready for generation", "state": session.state.value}
404
+
405
+ workflow = self._builder.generate_workflow(session)
406
+
407
+ # Save blueprint
408
+ if self._storage:
409
+ self._storage.save_blueprint(workflow.blueprint)
410
+ self._storage.save_session(session)
411
+
412
+ # Convert to JSON-serializable format
413
+ agents = []
414
+ for agent in workflow.blueprint.agents:
415
+ agents.append(
416
+ {
417
+ "agent_id": agent.agent_id,
418
+ "name": agent.name,
419
+ "role": agent.role.value,
420
+ "description": agent.description,
421
+ "tools": [t.tool_id for t in agent.tools],
422
+ }
423
+ )
424
+
425
+ stages = []
426
+ for stage in workflow.blueprint.stages:
427
+ stages.append(
428
+ {
429
+ "stage_id": stage.stage_id,
430
+ "name": stage.name,
431
+ "agent_ids": stage.agent_ids,
432
+ "dependencies": stage.dependencies,
433
+ }
434
+ )
435
+
436
+ metrics = []
437
+ for metric in workflow.success_criteria.metrics:
438
+ metrics.append(
439
+ {
440
+ "metric_id": metric.metric_id,
441
+ "name": metric.name,
442
+ "description": metric.description,
443
+ "type": metric.metric_type.value,
444
+ "target": metric.target_value,
445
+ }
446
+ )
447
+
448
+ return {
449
+ "session_id": session_id,
450
+ "blueprint_id": workflow.blueprint.blueprint_id,
451
+ "workflow_name": workflow.blueprint.name,
452
+ "agents": agents,
453
+ "stages": stages,
454
+ "success_metrics": metrics,
455
+ "state": session.state.value,
456
+ }
457
+
458
+ async def _handle_list_sessions(self, args: dict[str, Any]) -> dict[str, Any]:
459
+ """List all sessions."""
460
+ status_filter = args.get("status_filter", "all")
461
+
462
+ sessions = []
463
+
464
+ # Get from storage
465
+ if self._storage:
466
+ stored = self._storage.list_sessions()
467
+ for s in stored:
468
+ if status_filter == "all":
469
+ sessions.append(s)
470
+ elif status_filter == "active" and s.get("state") != "completed":
471
+ sessions.append(s)
472
+ elif status_filter == "completed" and s.get("state") == "completed":
473
+ sessions.append(s)
474
+
475
+ # Add in-memory sessions not yet persisted
476
+ for sid, session in self._sessions.items():
477
+ if not any(s.get("session_id") == sid for s in sessions):
478
+ sessions.append(
479
+ {
480
+ "session_id": session.session_id,
481
+ "state": session.state.value,
482
+ "goal": session.goal,
483
+ "created_at": (
484
+ session.created_at.isoformat() if session.created_at else None
485
+ ),
486
+ }
487
+ )
488
+
489
+ return {"sessions": sessions, "count": len(sessions)}
490
+
491
+ async def _handle_get_session(self, args: dict[str, Any]) -> dict[str, Any]:
492
+ """Get session details."""
493
+ session_id = args["session_id"]
494
+
495
+ session = self._get_session(session_id)
496
+ if not session:
497
+ return {"error": f"Session not found: {session_id}"}
498
+
499
+ result = {
500
+ "session_id": session.session_id,
501
+ "state": session.state.value,
502
+ "goal": session.goal,
503
+ "question_rounds": session.question_rounds,
504
+ "created_at": session.created_at.isoformat() if session.created_at else None,
505
+ "updated_at": session.updated_at.isoformat() if session.updated_at else None,
506
+ }
507
+ # Add domain from goal_analysis if available
508
+ if session.goal_analysis:
509
+ result["detected_domain"] = session.goal_analysis.domain
510
+ return result
511
+
512
+ async def _handle_list_blueprints(self, args: dict[str, Any]) -> dict[str, Any]:
513
+ """List all blueprints."""
514
+ domain_filter = args.get("domain_filter")
515
+
516
+ blueprints = []
517
+
518
+ if self._storage:
519
+ stored = self._storage.list_blueprints()
520
+ for bp in stored:
521
+ if domain_filter:
522
+ domains = bp.get("domains", [])
523
+ if domain_filter not in domains:
524
+ continue
525
+ blueprints.append(bp)
526
+
527
+ return {"blueprints": blueprints, "count": len(blueprints)}
528
+
529
+ async def _handle_analyze_goal(self, args: dict[str, Any]) -> dict[str, Any]:
530
+ """Analyze a goal without starting a session."""
531
+ goal = args["goal"]
532
+
533
+ # Use LLM analyzer if available
534
+ if self._llm_analyzer:
535
+ try:
536
+ analysis = await self._llm_analyzer.analyze_goal(goal)
537
+ return {
538
+ "goal": goal,
539
+ "primary_domain": analysis.primary_domain,
540
+ "secondary_domains": analysis.secondary_domains,
541
+ "confidence": analysis.confidence,
542
+ "detected_requirements": analysis.detected_requirements,
543
+ "ambiguities": analysis.ambiguities,
544
+ "suggested_questions": analysis.suggested_questions,
545
+ "suggested_agents": analysis.suggested_agents,
546
+ "analysis_method": "llm",
547
+ }
548
+ except Exception as e:
549
+ logger.warning(f"LLM analysis failed: {e}")
550
+
551
+ # Fallback to keyword-based analysis
552
+ domains = self._builder._detect_domains(goal)
553
+ return {"goal": goal, "detected_domains": list(domains), "analysis_method": "keyword"}
554
+
555
+ async def _handle_recommend_agents(self, args: dict[str, Any]) -> dict[str, Any]:
556
+ """Get agent recommendations."""
557
+ domains = args["domains"]
558
+ languages = args.get("languages", [])
559
+ quality_focus = args.get("quality_focus", [])
560
+
561
+ from .feedback import AdaptiveAgentGenerator
562
+ from .generator import AgentGenerator
563
+
564
+ # Use adaptive generator if feedback available
565
+ if self._feedback_loop:
566
+ adaptive_gen = AdaptiveAgentGenerator(self._feedback_loop.collector)
567
+ context = {"domains": domains, "languages": languages, "quality_focus": quality_focus}
568
+ recommendations = adaptive_gen.recommend_agents(context)
569
+ else:
570
+ # Use basic generator
571
+ generator = AgentGenerator()
572
+ recommendations = []
573
+ for domain in domains:
574
+ templates = generator._get_templates_for_domain(domain)
575
+ for t in templates:
576
+ recommendations.append({"template_id": t, "domain": domain, "confidence": 0.8})
577
+
578
+ return {"recommendations": recommendations, "count": len(recommendations)}
579
+
580
+ def _get_session(self, session_id: str):
581
+ """Get session from memory or storage."""
582
+ # Check memory first
583
+ if session_id in self._sessions:
584
+ return self._sessions[session_id]
585
+
586
+ # Try loading from storage
587
+ if self._storage:
588
+ session = self._storage.load_session(session_id)
589
+ if session:
590
+ self._sessions[session_id] = session
591
+ return session
592
+
593
+ return None
594
+
595
+
596
+ async def run_mcp_server():
597
+ """Run the MCP server using stdio transport."""
598
+ server = SocraticMCPServer()
599
+
600
+ # Read from stdin, write to stdout
601
+ reader = asyncio.StreamReader()
602
+ protocol = asyncio.StreamReaderProtocol(reader)
603
+ await asyncio.get_event_loop().connect_read_pipe(lambda: protocol, sys.stdin)
604
+
605
+ writer_transport, writer_protocol = await asyncio.get_event_loop().connect_write_pipe(
606
+ asyncio.streams.FlowControlMixin, sys.stdout
607
+ )
608
+ writer = asyncio.StreamWriter(
609
+ writer_transport, writer_protocol, reader, asyncio.get_event_loop()
610
+ )
611
+
612
+ async def send_response(response: dict):
613
+ """Send a JSON-RPC response."""
614
+ data = json.dumps(response) + "\n"
615
+ writer.write(data.encode())
616
+ await writer.drain()
617
+
618
+ async def handle_message(message: dict):
619
+ """Handle an incoming JSON-RPC message."""
620
+ method = message.get("method")
621
+ params = message.get("params", {})
622
+ msg_id = message.get("id")
623
+
624
+ if method == "initialize":
625
+ # MCP initialization
626
+ response = {
627
+ "jsonrpc": "2.0",
628
+ "id": msg_id,
629
+ "result": {
630
+ "protocolVersion": MCP_VERSION,
631
+ "capabilities": {"tools": {}},
632
+ "serverInfo": {"name": "socratic-workflow-builder", "version": "1.0.0"},
633
+ },
634
+ }
635
+ await send_response(response)
636
+
637
+ elif method == "tools/list":
638
+ # List available tools
639
+ response = {"jsonrpc": "2.0", "id": msg_id, "result": {"tools": SOCRATIC_TOOLS}}
640
+ await send_response(response)
641
+
642
+ elif method == "tools/call":
643
+ # Execute a tool
644
+ tool_name = params.get("name")
645
+ tool_args = params.get("arguments", {})
646
+
647
+ result = await server.handle_tool_call(tool_name, tool_args)
648
+
649
+ response = {
650
+ "jsonrpc": "2.0",
651
+ "id": msg_id,
652
+ "result": {"content": [{"type": "text", "text": json.dumps(result, indent=2)}]},
653
+ }
654
+ await send_response(response)
655
+
656
+ elif method == "notifications/initialized":
657
+ # Client initialized notification - no response needed
658
+ pass
659
+
660
+ else:
661
+ # Unknown method
662
+ response = {
663
+ "jsonrpc": "2.0",
664
+ "id": msg_id,
665
+ "error": {"code": -32601, "message": f"Method not found: {method}"},
666
+ }
667
+ await send_response(response)
668
+
669
+ # Main message loop
670
+ logger.info("Socratic MCP Server starting...")
671
+
672
+ while True:
673
+ try:
674
+ line = await reader.readline()
675
+ if not line:
676
+ break
677
+
678
+ message = json.loads(line.decode().strip())
679
+ await handle_message(message)
680
+
681
+ except json.JSONDecodeError as e:
682
+ logger.error(f"Invalid JSON: {e}")
683
+ except Exception as e:
684
+ logger.exception(f"Error processing message: {e}")
685
+
686
+
687
+ def main():
688
+ """Entry point for MCP server."""
689
+ logging.basicConfig(
690
+ level=logging.INFO,
691
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
692
+ stream=sys.stderr, # Log to stderr, MCP uses stdout
693
+ )
694
+
695
+ try:
696
+ asyncio.run(run_mcp_server())
697
+ except KeyboardInterrupt:
698
+ logger.info("Server shutting down...")
699
+
700
+
701
+ if __name__ == "__main__":
702
+ main()