PraisonAI 3.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 (393) hide show
  1. praisonai/__init__.py +54 -0
  2. praisonai/__main__.py +15 -0
  3. praisonai/acp/__init__.py +54 -0
  4. praisonai/acp/config.py +159 -0
  5. praisonai/acp/server.py +587 -0
  6. praisonai/acp/session.py +219 -0
  7. praisonai/adapters/__init__.py +50 -0
  8. praisonai/adapters/readers.py +395 -0
  9. praisonai/adapters/rerankers.py +315 -0
  10. praisonai/adapters/retrievers.py +394 -0
  11. praisonai/adapters/vector_stores.py +409 -0
  12. praisonai/agent_scheduler.py +337 -0
  13. praisonai/agents_generator.py +903 -0
  14. praisonai/api/call.py +292 -0
  15. praisonai/auto.py +1197 -0
  16. praisonai/capabilities/__init__.py +275 -0
  17. praisonai/capabilities/a2a.py +140 -0
  18. praisonai/capabilities/assistants.py +283 -0
  19. praisonai/capabilities/audio.py +320 -0
  20. praisonai/capabilities/batches.py +469 -0
  21. praisonai/capabilities/completions.py +336 -0
  22. praisonai/capabilities/container_files.py +155 -0
  23. praisonai/capabilities/containers.py +93 -0
  24. praisonai/capabilities/embeddings.py +158 -0
  25. praisonai/capabilities/files.py +467 -0
  26. praisonai/capabilities/fine_tuning.py +293 -0
  27. praisonai/capabilities/guardrails.py +182 -0
  28. praisonai/capabilities/images.py +330 -0
  29. praisonai/capabilities/mcp.py +190 -0
  30. praisonai/capabilities/messages.py +270 -0
  31. praisonai/capabilities/moderations.py +154 -0
  32. praisonai/capabilities/ocr.py +217 -0
  33. praisonai/capabilities/passthrough.py +204 -0
  34. praisonai/capabilities/rag.py +207 -0
  35. praisonai/capabilities/realtime.py +160 -0
  36. praisonai/capabilities/rerank.py +165 -0
  37. praisonai/capabilities/responses.py +266 -0
  38. praisonai/capabilities/search.py +109 -0
  39. praisonai/capabilities/skills.py +133 -0
  40. praisonai/capabilities/vector_store_files.py +334 -0
  41. praisonai/capabilities/vector_stores.py +304 -0
  42. praisonai/capabilities/videos.py +141 -0
  43. praisonai/chainlit_ui.py +304 -0
  44. praisonai/chat/__init__.py +106 -0
  45. praisonai/chat/app.py +125 -0
  46. praisonai/cli/__init__.py +26 -0
  47. praisonai/cli/app.py +213 -0
  48. praisonai/cli/commands/__init__.py +75 -0
  49. praisonai/cli/commands/acp.py +70 -0
  50. praisonai/cli/commands/completion.py +333 -0
  51. praisonai/cli/commands/config.py +166 -0
  52. praisonai/cli/commands/debug.py +142 -0
  53. praisonai/cli/commands/diag.py +55 -0
  54. praisonai/cli/commands/doctor.py +166 -0
  55. praisonai/cli/commands/environment.py +179 -0
  56. praisonai/cli/commands/lsp.py +112 -0
  57. praisonai/cli/commands/mcp.py +210 -0
  58. praisonai/cli/commands/profile.py +457 -0
  59. praisonai/cli/commands/run.py +228 -0
  60. praisonai/cli/commands/schedule.py +150 -0
  61. praisonai/cli/commands/serve.py +97 -0
  62. praisonai/cli/commands/session.py +212 -0
  63. praisonai/cli/commands/traces.py +145 -0
  64. praisonai/cli/commands/version.py +101 -0
  65. praisonai/cli/configuration/__init__.py +18 -0
  66. praisonai/cli/configuration/loader.py +353 -0
  67. praisonai/cli/configuration/paths.py +114 -0
  68. praisonai/cli/configuration/schema.py +164 -0
  69. praisonai/cli/features/__init__.py +268 -0
  70. praisonai/cli/features/acp.py +236 -0
  71. praisonai/cli/features/action_orchestrator.py +546 -0
  72. praisonai/cli/features/agent_scheduler.py +773 -0
  73. praisonai/cli/features/agent_tools.py +474 -0
  74. praisonai/cli/features/agents.py +375 -0
  75. praisonai/cli/features/at_mentions.py +471 -0
  76. praisonai/cli/features/auto_memory.py +182 -0
  77. praisonai/cli/features/autonomy_mode.py +490 -0
  78. praisonai/cli/features/background.py +356 -0
  79. praisonai/cli/features/base.py +168 -0
  80. praisonai/cli/features/capabilities.py +1326 -0
  81. praisonai/cli/features/checkpoints.py +338 -0
  82. praisonai/cli/features/code_intelligence.py +652 -0
  83. praisonai/cli/features/compaction.py +294 -0
  84. praisonai/cli/features/compare.py +534 -0
  85. praisonai/cli/features/cost_tracker.py +514 -0
  86. praisonai/cli/features/debug.py +810 -0
  87. praisonai/cli/features/deploy.py +517 -0
  88. praisonai/cli/features/diag.py +289 -0
  89. praisonai/cli/features/doctor/__init__.py +63 -0
  90. praisonai/cli/features/doctor/checks/__init__.py +24 -0
  91. praisonai/cli/features/doctor/checks/acp_checks.py +240 -0
  92. praisonai/cli/features/doctor/checks/config_checks.py +366 -0
  93. praisonai/cli/features/doctor/checks/db_checks.py +366 -0
  94. praisonai/cli/features/doctor/checks/env_checks.py +543 -0
  95. praisonai/cli/features/doctor/checks/lsp_checks.py +199 -0
  96. praisonai/cli/features/doctor/checks/mcp_checks.py +349 -0
  97. praisonai/cli/features/doctor/checks/memory_checks.py +268 -0
  98. praisonai/cli/features/doctor/checks/network_checks.py +251 -0
  99. praisonai/cli/features/doctor/checks/obs_checks.py +328 -0
  100. praisonai/cli/features/doctor/checks/performance_checks.py +235 -0
  101. praisonai/cli/features/doctor/checks/permissions_checks.py +259 -0
  102. praisonai/cli/features/doctor/checks/selftest_checks.py +322 -0
  103. praisonai/cli/features/doctor/checks/serve_checks.py +426 -0
  104. praisonai/cli/features/doctor/checks/skills_checks.py +231 -0
  105. praisonai/cli/features/doctor/checks/tools_checks.py +371 -0
  106. praisonai/cli/features/doctor/engine.py +266 -0
  107. praisonai/cli/features/doctor/formatters.py +310 -0
  108. praisonai/cli/features/doctor/handler.py +397 -0
  109. praisonai/cli/features/doctor/models.py +264 -0
  110. praisonai/cli/features/doctor/registry.py +239 -0
  111. praisonai/cli/features/endpoints.py +1019 -0
  112. praisonai/cli/features/eval.py +560 -0
  113. praisonai/cli/features/external_agents.py +231 -0
  114. praisonai/cli/features/fast_context.py +410 -0
  115. praisonai/cli/features/flow_display.py +566 -0
  116. praisonai/cli/features/git_integration.py +651 -0
  117. praisonai/cli/features/guardrail.py +171 -0
  118. praisonai/cli/features/handoff.py +185 -0
  119. praisonai/cli/features/hooks.py +583 -0
  120. praisonai/cli/features/image.py +384 -0
  121. praisonai/cli/features/interactive_runtime.py +585 -0
  122. praisonai/cli/features/interactive_tools.py +380 -0
  123. praisonai/cli/features/interactive_tui.py +603 -0
  124. praisonai/cli/features/jobs.py +632 -0
  125. praisonai/cli/features/knowledge.py +531 -0
  126. praisonai/cli/features/lite.py +244 -0
  127. praisonai/cli/features/lsp_cli.py +225 -0
  128. praisonai/cli/features/mcp.py +169 -0
  129. praisonai/cli/features/message_queue.py +587 -0
  130. praisonai/cli/features/metrics.py +211 -0
  131. praisonai/cli/features/n8n.py +673 -0
  132. praisonai/cli/features/observability.py +293 -0
  133. praisonai/cli/features/ollama.py +361 -0
  134. praisonai/cli/features/output_style.py +273 -0
  135. praisonai/cli/features/package.py +631 -0
  136. praisonai/cli/features/performance.py +308 -0
  137. praisonai/cli/features/persistence.py +636 -0
  138. praisonai/cli/features/profile.py +226 -0
  139. praisonai/cli/features/profiler/__init__.py +81 -0
  140. praisonai/cli/features/profiler/core.py +558 -0
  141. praisonai/cli/features/profiler/optimizations.py +652 -0
  142. praisonai/cli/features/profiler/suite.py +386 -0
  143. praisonai/cli/features/profiling.py +350 -0
  144. praisonai/cli/features/queue/__init__.py +73 -0
  145. praisonai/cli/features/queue/manager.py +395 -0
  146. praisonai/cli/features/queue/models.py +286 -0
  147. praisonai/cli/features/queue/persistence.py +564 -0
  148. praisonai/cli/features/queue/scheduler.py +484 -0
  149. praisonai/cli/features/queue/worker.py +372 -0
  150. praisonai/cli/features/recipe.py +1723 -0
  151. praisonai/cli/features/recipes.py +449 -0
  152. praisonai/cli/features/registry.py +229 -0
  153. praisonai/cli/features/repo_map.py +860 -0
  154. praisonai/cli/features/router.py +466 -0
  155. praisonai/cli/features/sandbox_executor.py +515 -0
  156. praisonai/cli/features/serve.py +829 -0
  157. praisonai/cli/features/session.py +222 -0
  158. praisonai/cli/features/skills.py +856 -0
  159. praisonai/cli/features/slash_commands.py +650 -0
  160. praisonai/cli/features/telemetry.py +179 -0
  161. praisonai/cli/features/templates.py +1384 -0
  162. praisonai/cli/features/thinking.py +305 -0
  163. praisonai/cli/features/todo.py +334 -0
  164. praisonai/cli/features/tools.py +680 -0
  165. praisonai/cli/features/tui/__init__.py +83 -0
  166. praisonai/cli/features/tui/app.py +580 -0
  167. praisonai/cli/features/tui/cli.py +566 -0
  168. praisonai/cli/features/tui/debug.py +511 -0
  169. praisonai/cli/features/tui/events.py +99 -0
  170. praisonai/cli/features/tui/mock_provider.py +328 -0
  171. praisonai/cli/features/tui/orchestrator.py +652 -0
  172. praisonai/cli/features/tui/screens/__init__.py +50 -0
  173. praisonai/cli/features/tui/screens/main.py +245 -0
  174. praisonai/cli/features/tui/screens/queue.py +174 -0
  175. praisonai/cli/features/tui/screens/session.py +124 -0
  176. praisonai/cli/features/tui/screens/settings.py +148 -0
  177. praisonai/cli/features/tui/widgets/__init__.py +56 -0
  178. praisonai/cli/features/tui/widgets/chat.py +261 -0
  179. praisonai/cli/features/tui/widgets/composer.py +224 -0
  180. praisonai/cli/features/tui/widgets/queue_panel.py +200 -0
  181. praisonai/cli/features/tui/widgets/status.py +167 -0
  182. praisonai/cli/features/tui/widgets/tool_panel.py +248 -0
  183. praisonai/cli/features/workflow.py +720 -0
  184. praisonai/cli/legacy.py +236 -0
  185. praisonai/cli/main.py +5559 -0
  186. praisonai/cli/schedule_cli.py +54 -0
  187. praisonai/cli/state/__init__.py +31 -0
  188. praisonai/cli/state/identifiers.py +161 -0
  189. praisonai/cli/state/sessions.py +313 -0
  190. praisonai/code/__init__.py +93 -0
  191. praisonai/code/agent_tools.py +344 -0
  192. praisonai/code/diff/__init__.py +21 -0
  193. praisonai/code/diff/diff_strategy.py +432 -0
  194. praisonai/code/tools/__init__.py +27 -0
  195. praisonai/code/tools/apply_diff.py +221 -0
  196. praisonai/code/tools/execute_command.py +275 -0
  197. praisonai/code/tools/list_files.py +274 -0
  198. praisonai/code/tools/read_file.py +206 -0
  199. praisonai/code/tools/search_replace.py +248 -0
  200. praisonai/code/tools/write_file.py +217 -0
  201. praisonai/code/utils/__init__.py +46 -0
  202. praisonai/code/utils/file_utils.py +307 -0
  203. praisonai/code/utils/ignore_utils.py +308 -0
  204. praisonai/code/utils/text_utils.py +276 -0
  205. praisonai/db/__init__.py +64 -0
  206. praisonai/db/adapter.py +531 -0
  207. praisonai/deploy/__init__.py +62 -0
  208. praisonai/deploy/api.py +231 -0
  209. praisonai/deploy/docker.py +454 -0
  210. praisonai/deploy/doctor.py +367 -0
  211. praisonai/deploy/main.py +327 -0
  212. praisonai/deploy/models.py +179 -0
  213. praisonai/deploy/providers/__init__.py +33 -0
  214. praisonai/deploy/providers/aws.py +331 -0
  215. praisonai/deploy/providers/azure.py +358 -0
  216. praisonai/deploy/providers/base.py +101 -0
  217. praisonai/deploy/providers/gcp.py +314 -0
  218. praisonai/deploy/schema.py +208 -0
  219. praisonai/deploy.py +185 -0
  220. praisonai/endpoints/__init__.py +53 -0
  221. praisonai/endpoints/a2u_server.py +410 -0
  222. praisonai/endpoints/discovery.py +165 -0
  223. praisonai/endpoints/providers/__init__.py +28 -0
  224. praisonai/endpoints/providers/a2a.py +253 -0
  225. praisonai/endpoints/providers/a2u.py +208 -0
  226. praisonai/endpoints/providers/agents_api.py +171 -0
  227. praisonai/endpoints/providers/base.py +231 -0
  228. praisonai/endpoints/providers/mcp.py +263 -0
  229. praisonai/endpoints/providers/recipe.py +206 -0
  230. praisonai/endpoints/providers/tools_mcp.py +150 -0
  231. praisonai/endpoints/registry.py +131 -0
  232. praisonai/endpoints/server.py +161 -0
  233. praisonai/inbuilt_tools/__init__.py +24 -0
  234. praisonai/inbuilt_tools/autogen_tools.py +117 -0
  235. praisonai/inc/__init__.py +2 -0
  236. praisonai/inc/config.py +96 -0
  237. praisonai/inc/models.py +155 -0
  238. praisonai/integrations/__init__.py +56 -0
  239. praisonai/integrations/base.py +303 -0
  240. praisonai/integrations/claude_code.py +270 -0
  241. praisonai/integrations/codex_cli.py +255 -0
  242. praisonai/integrations/cursor_cli.py +195 -0
  243. praisonai/integrations/gemini_cli.py +222 -0
  244. praisonai/jobs/__init__.py +67 -0
  245. praisonai/jobs/executor.py +425 -0
  246. praisonai/jobs/models.py +230 -0
  247. praisonai/jobs/router.py +314 -0
  248. praisonai/jobs/server.py +186 -0
  249. praisonai/jobs/store.py +203 -0
  250. praisonai/llm/__init__.py +66 -0
  251. praisonai/llm/registry.py +382 -0
  252. praisonai/mcp_server/__init__.py +152 -0
  253. praisonai/mcp_server/adapters/__init__.py +74 -0
  254. praisonai/mcp_server/adapters/agents.py +128 -0
  255. praisonai/mcp_server/adapters/capabilities.py +168 -0
  256. praisonai/mcp_server/adapters/cli_tools.py +568 -0
  257. praisonai/mcp_server/adapters/extended_capabilities.py +462 -0
  258. praisonai/mcp_server/adapters/knowledge.py +93 -0
  259. praisonai/mcp_server/adapters/memory.py +104 -0
  260. praisonai/mcp_server/adapters/prompts.py +306 -0
  261. praisonai/mcp_server/adapters/resources.py +124 -0
  262. praisonai/mcp_server/adapters/tools_bridge.py +280 -0
  263. praisonai/mcp_server/auth/__init__.py +48 -0
  264. praisonai/mcp_server/auth/api_key.py +291 -0
  265. praisonai/mcp_server/auth/oauth.py +460 -0
  266. praisonai/mcp_server/auth/oidc.py +289 -0
  267. praisonai/mcp_server/auth/scopes.py +260 -0
  268. praisonai/mcp_server/cli.py +852 -0
  269. praisonai/mcp_server/elicitation.py +445 -0
  270. praisonai/mcp_server/icons.py +302 -0
  271. praisonai/mcp_server/recipe_adapter.py +573 -0
  272. praisonai/mcp_server/recipe_cli.py +824 -0
  273. praisonai/mcp_server/registry.py +703 -0
  274. praisonai/mcp_server/sampling.py +422 -0
  275. praisonai/mcp_server/server.py +490 -0
  276. praisonai/mcp_server/tasks.py +443 -0
  277. praisonai/mcp_server/transports/__init__.py +18 -0
  278. praisonai/mcp_server/transports/http_stream.py +376 -0
  279. praisonai/mcp_server/transports/stdio.py +132 -0
  280. praisonai/persistence/__init__.py +84 -0
  281. praisonai/persistence/config.py +238 -0
  282. praisonai/persistence/conversation/__init__.py +25 -0
  283. praisonai/persistence/conversation/async_mysql.py +427 -0
  284. praisonai/persistence/conversation/async_postgres.py +410 -0
  285. praisonai/persistence/conversation/async_sqlite.py +371 -0
  286. praisonai/persistence/conversation/base.py +151 -0
  287. praisonai/persistence/conversation/json_store.py +250 -0
  288. praisonai/persistence/conversation/mysql.py +387 -0
  289. praisonai/persistence/conversation/postgres.py +401 -0
  290. praisonai/persistence/conversation/singlestore.py +240 -0
  291. praisonai/persistence/conversation/sqlite.py +341 -0
  292. praisonai/persistence/conversation/supabase.py +203 -0
  293. praisonai/persistence/conversation/surrealdb.py +287 -0
  294. praisonai/persistence/factory.py +301 -0
  295. praisonai/persistence/hooks/__init__.py +18 -0
  296. praisonai/persistence/hooks/agent_hooks.py +297 -0
  297. praisonai/persistence/knowledge/__init__.py +26 -0
  298. praisonai/persistence/knowledge/base.py +144 -0
  299. praisonai/persistence/knowledge/cassandra.py +232 -0
  300. praisonai/persistence/knowledge/chroma.py +295 -0
  301. praisonai/persistence/knowledge/clickhouse.py +242 -0
  302. praisonai/persistence/knowledge/cosmosdb_vector.py +438 -0
  303. praisonai/persistence/knowledge/couchbase.py +286 -0
  304. praisonai/persistence/knowledge/lancedb.py +216 -0
  305. praisonai/persistence/knowledge/langchain_adapter.py +291 -0
  306. praisonai/persistence/knowledge/lightrag_adapter.py +212 -0
  307. praisonai/persistence/knowledge/llamaindex_adapter.py +256 -0
  308. praisonai/persistence/knowledge/milvus.py +277 -0
  309. praisonai/persistence/knowledge/mongodb_vector.py +306 -0
  310. praisonai/persistence/knowledge/pgvector.py +335 -0
  311. praisonai/persistence/knowledge/pinecone.py +253 -0
  312. praisonai/persistence/knowledge/qdrant.py +301 -0
  313. praisonai/persistence/knowledge/redis_vector.py +291 -0
  314. praisonai/persistence/knowledge/singlestore_vector.py +299 -0
  315. praisonai/persistence/knowledge/surrealdb_vector.py +309 -0
  316. praisonai/persistence/knowledge/upstash_vector.py +266 -0
  317. praisonai/persistence/knowledge/weaviate.py +223 -0
  318. praisonai/persistence/migrations/__init__.py +10 -0
  319. praisonai/persistence/migrations/manager.py +251 -0
  320. praisonai/persistence/orchestrator.py +406 -0
  321. praisonai/persistence/state/__init__.py +21 -0
  322. praisonai/persistence/state/async_mongodb.py +200 -0
  323. praisonai/persistence/state/base.py +107 -0
  324. praisonai/persistence/state/dynamodb.py +226 -0
  325. praisonai/persistence/state/firestore.py +175 -0
  326. praisonai/persistence/state/gcs.py +155 -0
  327. praisonai/persistence/state/memory.py +245 -0
  328. praisonai/persistence/state/mongodb.py +158 -0
  329. praisonai/persistence/state/redis.py +190 -0
  330. praisonai/persistence/state/upstash.py +144 -0
  331. praisonai/persistence/tests/__init__.py +3 -0
  332. praisonai/persistence/tests/test_all_backends.py +633 -0
  333. praisonai/profiler.py +1214 -0
  334. praisonai/recipe/__init__.py +134 -0
  335. praisonai/recipe/bridge.py +278 -0
  336. praisonai/recipe/core.py +893 -0
  337. praisonai/recipe/exceptions.py +54 -0
  338. praisonai/recipe/history.py +402 -0
  339. praisonai/recipe/models.py +266 -0
  340. praisonai/recipe/operations.py +440 -0
  341. praisonai/recipe/policy.py +422 -0
  342. praisonai/recipe/registry.py +849 -0
  343. praisonai/recipe/runtime.py +214 -0
  344. praisonai/recipe/security.py +711 -0
  345. praisonai/recipe/serve.py +859 -0
  346. praisonai/recipe/server.py +613 -0
  347. praisonai/scheduler/__init__.py +45 -0
  348. praisonai/scheduler/agent_scheduler.py +552 -0
  349. praisonai/scheduler/base.py +124 -0
  350. praisonai/scheduler/daemon_manager.py +225 -0
  351. praisonai/scheduler/state_manager.py +155 -0
  352. praisonai/scheduler/yaml_loader.py +193 -0
  353. praisonai/scheduler.py +194 -0
  354. praisonai/setup/__init__.py +1 -0
  355. praisonai/setup/build.py +21 -0
  356. praisonai/setup/post_install.py +23 -0
  357. praisonai/setup/setup_conda_env.py +25 -0
  358. praisonai/setup.py +16 -0
  359. praisonai/templates/__init__.py +116 -0
  360. praisonai/templates/cache.py +364 -0
  361. praisonai/templates/dependency_checker.py +358 -0
  362. praisonai/templates/discovery.py +391 -0
  363. praisonai/templates/loader.py +564 -0
  364. praisonai/templates/registry.py +511 -0
  365. praisonai/templates/resolver.py +206 -0
  366. praisonai/templates/security.py +327 -0
  367. praisonai/templates/tool_override.py +498 -0
  368. praisonai/templates/tools_doctor.py +256 -0
  369. praisonai/test.py +105 -0
  370. praisonai/train.py +562 -0
  371. praisonai/train_vision.py +306 -0
  372. praisonai/ui/agents.py +824 -0
  373. praisonai/ui/callbacks.py +57 -0
  374. praisonai/ui/chainlit_compat.py +246 -0
  375. praisonai/ui/chat.py +532 -0
  376. praisonai/ui/code.py +717 -0
  377. praisonai/ui/colab.py +474 -0
  378. praisonai/ui/colab_chainlit.py +81 -0
  379. praisonai/ui/components/aicoder.py +284 -0
  380. praisonai/ui/context.py +283 -0
  381. praisonai/ui/database_config.py +56 -0
  382. praisonai/ui/db.py +294 -0
  383. praisonai/ui/realtime.py +488 -0
  384. praisonai/ui/realtimeclient/__init__.py +756 -0
  385. praisonai/ui/realtimeclient/tools.py +242 -0
  386. praisonai/ui/sql_alchemy.py +710 -0
  387. praisonai/upload_vision.py +140 -0
  388. praisonai/version.py +1 -0
  389. praisonai-3.0.0.dist-info/METADATA +3493 -0
  390. praisonai-3.0.0.dist-info/RECORD +393 -0
  391. praisonai-3.0.0.dist-info/WHEEL +5 -0
  392. praisonai-3.0.0.dist-info/entry_points.txt +4 -0
  393. praisonai-3.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,824 @@
1
+ """
2
+ Recipe MCP Server CLI Commands
3
+
4
+ CLI commands for serving recipes as MCP servers.
5
+
6
+ Commands:
7
+ - serve-recipe: Serve a recipe as MCP server
8
+ - list-recipes: List available recipes for MCP serving
9
+ - validate-recipe: Validate recipe MCP compatibility
10
+ - inspect-recipe: Inspect recipe MCP schema
11
+ - config-generate-recipe: Generate client config for recipe server
12
+ """
13
+
14
+ import argparse
15
+ import json
16
+ import logging
17
+ import sys
18
+ from typing import List
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ class RecipeMCPCLI:
24
+ """CLI handler for recipe MCP server commands."""
25
+
26
+ EXIT_SUCCESS = 0
27
+ EXIT_ERROR = 1
28
+
29
+ def __init__(self):
30
+ """Initialize CLI handler."""
31
+ pass
32
+
33
+ def handle(self, args: List[str]) -> int:
34
+ """
35
+ Handle recipe MCP CLI subcommand.
36
+
37
+ Args:
38
+ args: Command arguments
39
+
40
+ Returns:
41
+ Exit code
42
+ """
43
+ if not args:
44
+ self._print_help()
45
+ return self.EXIT_SUCCESS
46
+
47
+ command = args[0]
48
+ remaining = args[1:]
49
+
50
+ commands = {
51
+ "serve-recipe": self.cmd_serve_recipe,
52
+ "list-recipes": self.cmd_list_recipes,
53
+ "validate-recipe": self.cmd_validate_recipe,
54
+ "inspect-recipe": self.cmd_inspect_recipe,
55
+ "config-generate-recipe": self.cmd_config_generate_recipe,
56
+ "auth": self.cmd_auth,
57
+ "tasks": self.cmd_tasks,
58
+ "help": lambda _: self._print_help() or self.EXIT_SUCCESS,
59
+ "--help": lambda _: self._print_help() or self.EXIT_SUCCESS,
60
+ "-h": lambda _: self._print_help() or self.EXIT_SUCCESS,
61
+ }
62
+
63
+ if command in commands:
64
+ return commands[command](remaining)
65
+ else:
66
+ self._print_error(f"Unknown command: {command}")
67
+ self._print_help()
68
+ return self.EXIT_ERROR
69
+
70
+ def _print_help(self) -> None:
71
+ """Print help message."""
72
+ help_text = """
73
+ [bold cyan]PraisonAI Recipe MCP Server (Protocol Version 2025-11-25)[/bold cyan]
74
+
75
+ Serve PraisonAI recipes as MCP servers for Claude Desktop, Cursor, Windsurf, and other MCP clients.
76
+
77
+ [bold]Usage:[/bold]
78
+ praisonai mcp <command> [options]
79
+
80
+ [bold]Recipe Commands:[/bold]
81
+ serve-recipe <name> Serve a recipe as MCP server
82
+ list-recipes List available recipes
83
+ validate-recipe <name> Validate recipe MCP compatibility
84
+ inspect-recipe <name> Inspect recipe MCP schema
85
+ config-generate-recipe Generate client config for recipe
86
+
87
+ [bold]Auth Commands:[/bold]
88
+ auth generate-key Generate API key
89
+ auth validate <key> Validate API key
90
+ auth oidc-discover <url> Discover OIDC configuration
91
+
92
+ [bold]Tasks Commands:[/bold]
93
+ tasks list List tasks
94
+ tasks get <id> Get task details
95
+ tasks cancel <id> Cancel a task
96
+
97
+ [bold]Serve Recipe Options:[/bold]
98
+ --transport <type> Transport: stdio (default) or http-stream
99
+ --host <host> HTTP host (default: 127.0.0.1)
100
+ --port <port> HTTP port (default: 8080)
101
+ --endpoint <path> HTTP endpoint (default: /mcp)
102
+ --api-key <key> API key for authentication
103
+ --safe-mode/--no-safe-mode Enable/disable safe mode (default: enabled)
104
+ --expose-tools/--no-expose-tools Expose agent tools (default: yes)
105
+ --expose-prompts/--no-expose-prompts Expose prompts (default: yes)
106
+ --session-ttl <secs> Session TTL in seconds (default: 3600)
107
+ --log-level <level> Log level: debug, info, warning, error
108
+ --json Output in JSON format
109
+
110
+ [bold]Examples:[/bold]
111
+ # Serve a recipe as STDIO MCP server
112
+ praisonai mcp serve-recipe support-reply --transport stdio
113
+
114
+ # Serve with HTTP Stream transport
115
+ praisonai mcp serve-recipe ai-video-editor --transport http-stream --port 8080
116
+
117
+ # List available recipes
118
+ praisonai mcp list-recipes
119
+
120
+ # Validate recipe MCP compatibility
121
+ praisonai mcp validate-recipe support-reply
122
+
123
+ # Inspect recipe tools/resources/prompts
124
+ praisonai mcp inspect-recipe support-reply --tools
125
+
126
+ # Generate Claude Desktop config for recipe
127
+ praisonai mcp config-generate-recipe support-reply --client claude-desktop
128
+ """
129
+ self._print_rich(help_text)
130
+
131
+ def _print_rich(self, text: str) -> None:
132
+ """Print with rich formatting if available."""
133
+ try:
134
+ from rich import print as rprint
135
+ rprint(text)
136
+ except ImportError:
137
+ import re
138
+ plain = re.sub(r'\[/?[^\]]+\]', '', text)
139
+ print(plain)
140
+
141
+ def _print_error(self, message: str) -> None:
142
+ """Print error message."""
143
+ try:
144
+ from rich import print as rprint
145
+ rprint(f"[red]Error: {message}[/red]")
146
+ except ImportError:
147
+ print(f"Error: {message}", file=sys.stderr)
148
+
149
+ def _print_success(self, message: str) -> None:
150
+ """Print success message."""
151
+ try:
152
+ from rich import print as rprint
153
+ rprint(f"[green]✓ {message}[/green]")
154
+ except ImportError:
155
+ print(f"✓ {message}")
156
+
157
+ def _print_json(self, data: dict) -> None:
158
+ """Print JSON output."""
159
+ print(json.dumps(data, indent=2))
160
+
161
+ def cmd_serve_recipe(self, args: List[str]) -> int:
162
+ """Serve a recipe as MCP server."""
163
+ parser = argparse.ArgumentParser(prog="praisonai mcp serve-recipe")
164
+ parser.add_argument("recipe_name", help="Recipe name to serve")
165
+ parser.add_argument("--transport", default="stdio", choices=["stdio", "http-stream"])
166
+ parser.add_argument("--host", default="127.0.0.1")
167
+ parser.add_argument("--port", type=int, default=8080)
168
+ parser.add_argument("--endpoint", default="/mcp")
169
+ parser.add_argument("--api-key", default=None)
170
+ parser.add_argument("--safe-mode", action="store_true", default=True)
171
+ parser.add_argument("--no-safe-mode", action="store_true")
172
+ parser.add_argument("--expose-tools", action="store_true", default=True)
173
+ parser.add_argument("--no-expose-tools", action="store_true")
174
+ parser.add_argument("--expose-prompts", action="store_true", default=True)
175
+ parser.add_argument("--no-expose-prompts", action="store_true")
176
+ parser.add_argument("--session-ttl", type=int, default=3600)
177
+ parser.add_argument("--log-level", default="warning", choices=["debug", "info", "warning", "error"])
178
+ parser.add_argument("--json", action="store_true")
179
+
180
+ try:
181
+ parsed = parser.parse_args(args)
182
+ except SystemExit:
183
+ return self.EXIT_ERROR
184
+
185
+ # Configure logging
186
+ log_levels = {
187
+ "debug": logging.DEBUG,
188
+ "info": logging.INFO,
189
+ "warning": logging.WARNING,
190
+ "error": logging.ERROR,
191
+ }
192
+ logging.basicConfig(level=log_levels.get(parsed.log_level, logging.WARNING), stream=sys.stderr)
193
+
194
+ try:
195
+ from .recipe_adapter import RecipeMCPAdapter, RecipeMCPConfig
196
+
197
+ # Create config
198
+ config = RecipeMCPConfig(
199
+ recipe_name=parsed.recipe_name,
200
+ safe_mode=not parsed.no_safe_mode,
201
+ expose_agent_tools=not parsed.no_expose_tools,
202
+ expose_prompts=not parsed.no_expose_prompts,
203
+ session_ttl=parsed.session_ttl,
204
+ )
205
+
206
+ # Create adapter and load
207
+ adapter = RecipeMCPAdapter(parsed.recipe_name, config)
208
+ adapter.load()
209
+
210
+ # Create server
211
+ server = adapter.to_mcp_server()
212
+
213
+ if parsed.transport == "stdio":
214
+ logger.info(f"Starting recipe MCP server '{parsed.recipe_name}' on STDIO transport")
215
+ server.run_stdio()
216
+ else:
217
+ if not parsed.json:
218
+ self._print_success(f"Starting recipe MCP server '{parsed.recipe_name}' on http://{parsed.host}:{parsed.port}{parsed.endpoint}")
219
+
220
+ server.run_http_stream(
221
+ host=parsed.host,
222
+ port=parsed.port,
223
+ endpoint=parsed.endpoint,
224
+ api_key=parsed.api_key,
225
+ session_ttl=parsed.session_ttl,
226
+ )
227
+
228
+ return self.EXIT_SUCCESS
229
+
230
+ except ValueError as e:
231
+ self._print_error(str(e))
232
+ return self.EXIT_ERROR
233
+ except ImportError as e:
234
+ self._print_error(f"Missing dependency: {e}")
235
+ return self.EXIT_ERROR
236
+ except KeyboardInterrupt:
237
+ logger.info("Server stopped by user")
238
+ return self.EXIT_SUCCESS
239
+ except Exception as e:
240
+ self._print_error(str(e))
241
+ return self.EXIT_ERROR
242
+
243
+ def cmd_list_recipes(self, args: List[str]) -> int:
244
+ """List available recipes."""
245
+ parser = argparse.ArgumentParser(prog="praisonai mcp list-recipes")
246
+ parser.add_argument("--tags", default=None, help="Filter by tags (comma-separated)")
247
+ parser.add_argument("--source", default=None, choices=["local", "package", "all"])
248
+ parser.add_argument("--json", action="store_true")
249
+
250
+ try:
251
+ parsed = parser.parse_args(args)
252
+ except SystemExit:
253
+ return self.EXIT_ERROR
254
+
255
+ try:
256
+ from ..recipe.core import list_recipes
257
+
258
+ tags = parsed.tags.split(",") if parsed.tags else None
259
+ recipes = list_recipes(tags=tags, source=parsed.source)
260
+
261
+ if parsed.json:
262
+ self._print_json({"recipes": [r.to_dict() for r in recipes]})
263
+ else:
264
+ if not recipes:
265
+ print("No recipes found")
266
+ return self.EXIT_SUCCESS
267
+
268
+ print(f"\n[bold]Available Recipes ({len(recipes)}):[/bold]\n")
269
+ for recipe in recipes:
270
+ print(f" • {recipe.name} (v{recipe.version})")
271
+ print(f" {recipe.description}")
272
+ if recipe.tags:
273
+ print(f" Tags: {', '.join(recipe.tags)}")
274
+ print()
275
+
276
+ return self.EXIT_SUCCESS
277
+
278
+ except ImportError:
279
+ # Fallback to template discovery
280
+ try:
281
+ from ..templates.discovery import TemplateDiscovery
282
+
283
+ discovery = TemplateDiscovery()
284
+ templates = discovery.discover_all()
285
+
286
+ if parsed.json:
287
+ self._print_json({"recipes": [t.to_dict() for t in templates]})
288
+ else:
289
+ if not templates:
290
+ print("No recipes found")
291
+ return self.EXIT_SUCCESS
292
+
293
+ print(f"\n[bold]Available Recipes ({len(templates)}):[/bold]\n")
294
+ for template in templates:
295
+ print(f" • {template.name}")
296
+ if template.description:
297
+ print(f" {template.description}")
298
+ print()
299
+
300
+ return self.EXIT_SUCCESS
301
+
302
+ except Exception as e:
303
+ self._print_error(str(e))
304
+ return self.EXIT_ERROR
305
+ except Exception as e:
306
+ self._print_error(str(e))
307
+ return self.EXIT_ERROR
308
+
309
+ def cmd_validate_recipe(self, args: List[str]) -> int:
310
+ """Validate recipe MCP compatibility."""
311
+ parser = argparse.ArgumentParser(prog="praisonai mcp validate-recipe")
312
+ parser.add_argument("recipe_name", help="Recipe name to validate")
313
+ parser.add_argument("--json", action="store_true")
314
+
315
+ try:
316
+ parsed = parser.parse_args(args)
317
+ except SystemExit:
318
+ return self.EXIT_ERROR
319
+
320
+ try:
321
+ from .recipe_adapter import RecipeMCPAdapter
322
+
323
+ errors = []
324
+ warnings = []
325
+
326
+ # Try to load the recipe
327
+ try:
328
+ adapter = RecipeMCPAdapter(parsed.recipe_name)
329
+ adapter.load()
330
+ except ValueError as e:
331
+ errors.append(f"Recipe not found: {e}")
332
+ except Exception as e:
333
+ errors.append(f"Failed to load recipe: {e}")
334
+
335
+ if not errors:
336
+ # Check tool registry
337
+ tools = adapter.get_tool_registry().list_all()
338
+ if not tools:
339
+ warnings.append("No tools registered")
340
+
341
+ # Check for dangerous tools
342
+ for tool in tools:
343
+ annotations = getattr(tool, 'annotations', {}) or {}
344
+ if annotations.get('type') == 'agent_tool':
345
+ original = annotations.get('original_tool', '')
346
+ if 'shell' in original.lower() or 'exec' in original.lower():
347
+ warnings.append(f"Tool '{tool.name}' may be dangerous (shell/exec)")
348
+
349
+ result = {
350
+ "valid": len(errors) == 0,
351
+ "recipe": parsed.recipe_name,
352
+ "errors": errors,
353
+ "warnings": warnings,
354
+ }
355
+
356
+ if parsed.json:
357
+ self._print_json(result)
358
+ else:
359
+ if result["valid"]:
360
+ self._print_success(f"Recipe '{parsed.recipe_name}' is valid for MCP serving")
361
+ else:
362
+ self._print_error(f"Recipe '{parsed.recipe_name}' has validation errors")
363
+
364
+ if errors:
365
+ print("\n[bold red]Errors:[/bold red]")
366
+ for error in errors:
367
+ print(f" ✗ {error}")
368
+
369
+ if warnings:
370
+ print("\n[bold yellow]Warnings:[/bold yellow]")
371
+ for warning in warnings:
372
+ print(f" ⚠ {warning}")
373
+
374
+ return self.EXIT_SUCCESS if result["valid"] else self.EXIT_ERROR
375
+
376
+ except Exception as e:
377
+ self._print_error(str(e))
378
+ return self.EXIT_ERROR
379
+
380
+ def cmd_inspect_recipe(self, args: List[str]) -> int:
381
+ """Inspect recipe MCP schema."""
382
+ parser = argparse.ArgumentParser(prog="praisonai mcp inspect-recipe")
383
+ parser.add_argument("recipe_name", help="Recipe name to inspect")
384
+ parser.add_argument("--tools", action="store_true", help="Show tools")
385
+ parser.add_argument("--resources", action="store_true", help="Show resources")
386
+ parser.add_argument("--prompts", action="store_true", help="Show prompts")
387
+ parser.add_argument("--metadata", action="store_true", help="Show metadata")
388
+ parser.add_argument("--json", action="store_true")
389
+
390
+ try:
391
+ parsed = parser.parse_args(args)
392
+ except SystemExit:
393
+ return self.EXIT_ERROR
394
+
395
+ # Default to showing all if nothing specified
396
+ show_all = not (parsed.tools or parsed.resources or parsed.prompts or parsed.metadata)
397
+
398
+ try:
399
+ from .recipe_adapter import RecipeMCPAdapter
400
+
401
+ adapter = RecipeMCPAdapter(parsed.recipe_name)
402
+ adapter.load()
403
+
404
+ result = {}
405
+
406
+ if show_all or parsed.metadata:
407
+ result["metadata"] = adapter.get_recipe_info()
408
+
409
+ if show_all or parsed.tools:
410
+ tools = adapter.get_tool_registry().list_schemas()
411
+ result["tools"] = tools
412
+
413
+ if show_all or parsed.resources:
414
+ resources = adapter.get_resource_registry().list_schemas()
415
+ result["resources"] = resources
416
+
417
+ if show_all or parsed.prompts:
418
+ prompts = adapter.get_prompt_registry().list_schemas()
419
+ result["prompts"] = prompts
420
+
421
+ if parsed.json:
422
+ self._print_json(result)
423
+ else:
424
+ print(f"\n[bold cyan]Recipe: {parsed.recipe_name}[/bold cyan]\n")
425
+
426
+ if "metadata" in result:
427
+ meta = result["metadata"]
428
+ print("[bold]Metadata:[/bold]")
429
+ print(f" Version: {meta.get('version', 'unknown')}")
430
+ print(f" Description: {meta.get('description', 'N/A')}")
431
+ if meta.get('tags'):
432
+ print(f" Tags: {', '.join(meta['tags'])}")
433
+ print()
434
+
435
+ if "tools" in result:
436
+ tools = result["tools"]
437
+ print(f"[bold]Tools ({len(tools)}):[/bold]")
438
+ for tool in tools:
439
+ print(f" • {tool['name']}")
440
+ print(f" {tool.get('description', 'No description')}")
441
+ print()
442
+
443
+ if "resources" in result:
444
+ resources = result["resources"]
445
+ print(f"[bold]Resources ({len(resources)}):[/bold]")
446
+ for res in resources:
447
+ print(f" • {res['uri']}")
448
+ print(f" {res.get('description', 'No description')}")
449
+ print()
450
+
451
+ if "prompts" in result:
452
+ prompts = result["prompts"]
453
+ print(f"[bold]Prompts ({len(prompts)}):[/bold]")
454
+ for prompt in prompts:
455
+ print(f" • {prompt['name']}")
456
+ print(f" {prompt.get('description', 'No description')}")
457
+ print()
458
+
459
+ return self.EXIT_SUCCESS
460
+
461
+ except Exception as e:
462
+ self._print_error(str(e))
463
+ return self.EXIT_ERROR
464
+
465
+ def cmd_config_generate_recipe(self, args: List[str]) -> int:
466
+ """Generate client config for recipe MCP server."""
467
+ parser = argparse.ArgumentParser(prog="praisonai mcp config-generate-recipe")
468
+ parser.add_argument("recipe_name", help="Recipe name")
469
+ parser.add_argument("--client", default="claude-desktop",
470
+ choices=["claude-desktop", "cursor", "vscode", "windsurf", "generic"])
471
+ parser.add_argument("--transport", default="stdio", choices=["stdio", "http-stream"])
472
+ parser.add_argument("--host", default="127.0.0.1")
473
+ parser.add_argument("--port", type=int, default=8080)
474
+ parser.add_argument("--output", default=None, help="Output file path")
475
+
476
+ try:
477
+ parsed = parser.parse_args(args)
478
+ except SystemExit:
479
+ return self.EXIT_ERROR
480
+
481
+ # Generate config
482
+ if parsed.transport == "stdio":
483
+ server_config = {
484
+ "command": "praisonai",
485
+ "args": ["mcp", "serve-recipe", parsed.recipe_name, "--transport", "stdio"],
486
+ }
487
+ else:
488
+ server_config = {
489
+ "url": f"http://{parsed.host}:{parsed.port}/mcp",
490
+ "transport": "http-stream",
491
+ }
492
+
493
+ if parsed.client == "claude-desktop":
494
+ config = {
495
+ "mcpServers": {
496
+ parsed.recipe_name: server_config
497
+ }
498
+ }
499
+ elif parsed.client in ("cursor", "windsurf"):
500
+ config = {
501
+ "mcpServers": {
502
+ parsed.recipe_name: server_config
503
+ }
504
+ }
505
+ elif parsed.client == "vscode":
506
+ config = {
507
+ "mcp.servers": {
508
+ parsed.recipe_name: server_config
509
+ }
510
+ }
511
+ else: # generic
512
+ config = {
513
+ "server": {
514
+ "name": parsed.recipe_name,
515
+ **server_config
516
+ }
517
+ }
518
+
519
+ config_json = json.dumps(config, indent=2)
520
+
521
+ if parsed.output:
522
+ with open(parsed.output, 'w') as f:
523
+ f.write(config_json)
524
+ self._print_success(f"Config written to {parsed.output}")
525
+ else:
526
+ print(f"\n[bold]{parsed.client} Configuration for {parsed.recipe_name}:[/bold]\n")
527
+ print(config_json)
528
+ print()
529
+
530
+ return self.EXIT_SUCCESS
531
+
532
+ def cmd_auth(self, args: List[str]) -> int:
533
+ """Handle auth subcommands."""
534
+ if not args:
535
+ print("Usage: praisonai mcp auth <subcommand>")
536
+ print("Subcommands: generate-key, validate, oidc-discover")
537
+ return self.EXIT_ERROR
538
+
539
+ subcommand = args[0]
540
+ remaining = args[1:]
541
+
542
+ if subcommand == "generate-key":
543
+ return self._auth_generate_key(remaining)
544
+ elif subcommand == "validate":
545
+ return self._auth_validate(remaining)
546
+ elif subcommand == "oidc-discover":
547
+ return self._auth_oidc_discover(remaining)
548
+ else:
549
+ self._print_error(f"Unknown auth subcommand: {subcommand}")
550
+ return self.EXIT_ERROR
551
+
552
+ def _auth_generate_key(self, args: List[str]) -> int:
553
+ """Generate API key."""
554
+ parser = argparse.ArgumentParser(prog="praisonai mcp auth generate-key")
555
+ parser.add_argument("--name", default=None, help="Key name")
556
+ parser.add_argument("--scopes", default=None, help="Comma-separated scopes")
557
+ parser.add_argument("--expires-in", type=int, default=None, help="Expiration in seconds")
558
+ parser.add_argument("--json", action="store_true")
559
+
560
+ try:
561
+ parsed = parser.parse_args(args)
562
+ except SystemExit:
563
+ return self.EXIT_ERROR
564
+
565
+ try:
566
+ from .auth.api_key import APIKeyAuth
567
+
568
+ auth = APIKeyAuth()
569
+ scopes = parsed.scopes.split(",") if parsed.scopes else None
570
+
571
+ raw_key, api_key = auth.generate_key(
572
+ name=parsed.name,
573
+ scopes=scopes,
574
+ expires_in=parsed.expires_in,
575
+ )
576
+
577
+ if parsed.json:
578
+ self._print_json({
579
+ "key": raw_key,
580
+ "key_id": api_key.key_id,
581
+ "name": api_key.name,
582
+ "scopes": api_key.scopes,
583
+ "expires_at": api_key.expires_at,
584
+ })
585
+ else:
586
+ print("\n[bold green]Generated API Key:[/bold green]")
587
+ print(f" Key: {raw_key}")
588
+ print(f" ID: {api_key.key_id}")
589
+ if api_key.name:
590
+ print(f" Name: {api_key.name}")
591
+ if api_key.scopes:
592
+ print(f" Scopes: {', '.join(api_key.scopes)}")
593
+ if api_key.expires_at:
594
+ print(f" Expires: {api_key.expires_at}")
595
+ print("\n[yellow]Save this key securely - it cannot be retrieved later.[/yellow]")
596
+
597
+ return self.EXIT_SUCCESS
598
+
599
+ except Exception as e:
600
+ self._print_error(str(e))
601
+ return self.EXIT_ERROR
602
+
603
+ def _auth_validate(self, args: List[str]) -> int:
604
+ """Validate API key."""
605
+ parser = argparse.ArgumentParser(prog="praisonai mcp auth validate")
606
+ parser.add_argument("key", help="API key to validate")
607
+ parser.add_argument("--json", action="store_true")
608
+
609
+ try:
610
+ parsed = parser.parse_args(args)
611
+ except SystemExit:
612
+ return self.EXIT_ERROR
613
+
614
+ try:
615
+ from .auth.api_key import APIKeyAuth
616
+
617
+ auth = APIKeyAuth()
618
+ is_valid, api_key = auth.validate(parsed.key)
619
+
620
+ if parsed.json:
621
+ self._print_json({
622
+ "valid": is_valid,
623
+ "key_id": api_key.key_id if api_key else None,
624
+ })
625
+ else:
626
+ if is_valid:
627
+ self._print_success("API key is valid")
628
+ else:
629
+ self._print_error("API key is invalid or expired")
630
+
631
+ return self.EXIT_SUCCESS if is_valid else self.EXIT_ERROR
632
+
633
+ except Exception as e:
634
+ self._print_error(str(e))
635
+ return self.EXIT_ERROR
636
+
637
+ def _auth_oidc_discover(self, args: List[str]) -> int:
638
+ """Discover OIDC configuration."""
639
+ parser = argparse.ArgumentParser(prog="praisonai mcp auth oidc-discover")
640
+ parser.add_argument("issuer", help="OIDC issuer URL")
641
+ parser.add_argument("--json", action="store_true")
642
+
643
+ try:
644
+ parsed = parser.parse_args(args)
645
+ except SystemExit:
646
+ return self.EXIT_ERROR
647
+
648
+ try:
649
+ import asyncio
650
+ from .auth.oidc import OIDCDiscovery
651
+
652
+ discovery = OIDCDiscovery()
653
+ config = asyncio.run(discovery.discover(parsed.issuer))
654
+
655
+ if parsed.json:
656
+ self._print_json(config.to_dict())
657
+ else:
658
+ print(f"\n[bold]OIDC Configuration for {parsed.issuer}:[/bold]\n")
659
+ print(f" Issuer: {config.issuer}")
660
+ print(f" Authorization: {config.authorization_endpoint}")
661
+ print(f" Token: {config.token_endpoint}")
662
+ if config.userinfo_endpoint:
663
+ print(f" UserInfo: {config.userinfo_endpoint}")
664
+ if config.scopes_supported:
665
+ print(f" Scopes: {', '.join(config.scopes_supported[:5])}...")
666
+
667
+ return self.EXIT_SUCCESS
668
+
669
+ except Exception as e:
670
+ self._print_error(str(e))
671
+ return self.EXIT_ERROR
672
+
673
+ def cmd_tasks(self, args: List[str]) -> int:
674
+ """Handle tasks subcommands."""
675
+ if not args:
676
+ print("Usage: praisonai mcp tasks <subcommand>")
677
+ print("Subcommands: list, get, cancel")
678
+ return self.EXIT_ERROR
679
+
680
+ subcommand = args[0]
681
+ remaining = args[1:]
682
+
683
+ if subcommand == "list":
684
+ return self._tasks_list(remaining)
685
+ elif subcommand == "get":
686
+ return self._tasks_get(remaining)
687
+ elif subcommand == "cancel":
688
+ return self._tasks_cancel(remaining)
689
+ else:
690
+ self._print_error(f"Unknown tasks subcommand: {subcommand}")
691
+ return self.EXIT_ERROR
692
+
693
+ def _tasks_list(self, args: List[str]) -> int:
694
+ """List tasks."""
695
+ parser = argparse.ArgumentParser(prog="praisonai mcp tasks list")
696
+ parser.add_argument("--session", default=None, help="Filter by session ID")
697
+ parser.add_argument("--state", default=None, choices=["pending", "running", "completed", "failed", "cancelled"])
698
+ parser.add_argument("--limit", type=int, default=20)
699
+ parser.add_argument("--json", action="store_true")
700
+
701
+ try:
702
+ parsed = parser.parse_args(args)
703
+ except SystemExit:
704
+ return self.EXIT_ERROR
705
+
706
+ try:
707
+ from .tasks import get_task_manager, TaskState
708
+
709
+ manager = get_task_manager()
710
+ state = TaskState(parsed.state) if parsed.state else None
711
+ tasks = manager.list_tasks(
712
+ session_id=parsed.session,
713
+ state=state,
714
+ limit=parsed.limit,
715
+ )
716
+
717
+ if parsed.json:
718
+ self._print_json({"tasks": [t.to_dict() for t in tasks]})
719
+ else:
720
+ if not tasks:
721
+ print("No tasks found")
722
+ return self.EXIT_SUCCESS
723
+
724
+ print(f"\n[bold]Tasks ({len(tasks)}):[/bold]\n")
725
+ for task in tasks:
726
+ print(f" • {task.id} [{task.state.value}]")
727
+ print(f" Method: {task.method}")
728
+ if task.progress:
729
+ print(f" Progress: {task.progress.current}/{task.progress.total or '?'}")
730
+ print()
731
+
732
+ return self.EXIT_SUCCESS
733
+
734
+ except Exception as e:
735
+ self._print_error(str(e))
736
+ return self.EXIT_ERROR
737
+
738
+ def _tasks_get(self, args: List[str]) -> int:
739
+ """Get task details."""
740
+ parser = argparse.ArgumentParser(prog="praisonai mcp tasks get")
741
+ parser.add_argument("task_id", help="Task ID")
742
+ parser.add_argument("--json", action="store_true")
743
+
744
+ try:
745
+ parsed = parser.parse_args(args)
746
+ except SystemExit:
747
+ return self.EXIT_ERROR
748
+
749
+ try:
750
+ from .tasks import get_task_manager
751
+
752
+ manager = get_task_manager()
753
+ task = manager.get_task(parsed.task_id)
754
+
755
+ if not task:
756
+ self._print_error(f"Task not found: {parsed.task_id}")
757
+ return self.EXIT_ERROR
758
+
759
+ if parsed.json:
760
+ self._print_json(task.to_dict())
761
+ else:
762
+ print(f"\n[bold]Task: {task.id}[/bold]\n")
763
+ print(f" State: {task.state.value}")
764
+ print(f" Method: {task.method}")
765
+ print(f" Created: {task.created_at}")
766
+ if task.progress:
767
+ print(f" Progress: {task.progress.current}/{task.progress.total or '?'}")
768
+ if task.result:
769
+ print(f" Result: {task.result}")
770
+ if task.error:
771
+ print(f" Error: {task.error}")
772
+
773
+ return self.EXIT_SUCCESS
774
+
775
+ except Exception as e:
776
+ self._print_error(str(e))
777
+ return self.EXIT_ERROR
778
+
779
+ def _tasks_cancel(self, args: List[str]) -> int:
780
+ """Cancel a task."""
781
+ parser = argparse.ArgumentParser(prog="praisonai mcp tasks cancel")
782
+ parser.add_argument("task_id", help="Task ID")
783
+ parser.add_argument("--json", action="store_true")
784
+
785
+ try:
786
+ parsed = parser.parse_args(args)
787
+ except SystemExit:
788
+ return self.EXIT_ERROR
789
+
790
+ try:
791
+ import asyncio
792
+ from .tasks import get_task_manager
793
+
794
+ manager = get_task_manager()
795
+ task = asyncio.run(manager.cancel_task(parsed.task_id))
796
+
797
+ if not task:
798
+ self._print_error(f"Task not found: {parsed.task_id}")
799
+ return self.EXIT_ERROR
800
+
801
+ if parsed.json:
802
+ self._print_json({"cancelled": True, "task": task.to_dict()})
803
+ else:
804
+ self._print_success(f"Task {parsed.task_id} cancelled")
805
+
806
+ return self.EXIT_SUCCESS
807
+
808
+ except Exception as e:
809
+ self._print_error(str(e))
810
+ return self.EXIT_ERROR
811
+
812
+
813
+ def handle_recipe_mcp_command(args: List[str]) -> int:
814
+ """
815
+ Entry point for recipe MCP CLI commands.
816
+
817
+ Args:
818
+ args: Command arguments
819
+
820
+ Returns:
821
+ Exit code
822
+ """
823
+ cli = RecipeMCPCLI()
824
+ return cli.handle(args)