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,573 @@
1
+ """
2
+ Recipe MCP Adapter
3
+
4
+ Adapts PraisonAI recipes to MCP server primitives (tools, resources, prompts).
5
+ Enables any recipe to be served as an MCP server.
6
+
7
+ MCP Protocol Version: 2025-11-25
8
+ """
9
+
10
+ import asyncio
11
+ import logging
12
+ import re
13
+ from dataclasses import dataclass
14
+ from pathlib import Path
15
+ from typing import Any, Callable, Dict, List, Optional, Set, TYPE_CHECKING, Union
16
+
17
+ if TYPE_CHECKING:
18
+ from .server import MCPServer
19
+
20
+ from .registry import (
21
+ MCPToolRegistry,
22
+ MCPResourceRegistry,
23
+ MCPPromptRegistry,
24
+ )
25
+
26
+ logger = logging.getLogger(__name__)
27
+
28
+ # Default denied tools (dangerous by default)
29
+ DEFAULT_DENIED_TOOLS: Set[str] = {
30
+ "shell.exec",
31
+ "shell.run",
32
+ "shell_tool",
33
+ "file.write",
34
+ "file.delete",
35
+ "fs.write",
36
+ "fs.delete",
37
+ "network.unrestricted",
38
+ "db.write",
39
+ "db.delete",
40
+ "execute_command",
41
+ "os.system",
42
+ "subprocess.run",
43
+ "eval",
44
+ "exec",
45
+ }
46
+
47
+
48
+ @dataclass
49
+ class RecipeMCPConfig:
50
+ """Configuration for serving a recipe as MCP server."""
51
+
52
+ recipe_name: str
53
+
54
+ # Tool exposure settings
55
+ expose_agent_tools: bool = True
56
+ expose_run_tool: bool = True
57
+ tool_namespace: str = "prefixed" # "flat", "nested", or "prefixed"
58
+
59
+ # Resource exposure settings
60
+ expose_config: bool = True
61
+ expose_outputs: bool = True
62
+
63
+ # Prompt exposure settings
64
+ expose_prompts: bool = True
65
+ expose_agent_instructions: bool = True
66
+
67
+ # Security settings
68
+ safe_mode: bool = True
69
+ tool_allowlist: Optional[List[str]] = None
70
+ tool_denylist: Optional[List[str]] = None
71
+ workspace_path: Optional[str] = None
72
+ allow_network: bool = False
73
+ env_allowlist: Optional[List[str]] = None
74
+
75
+ # Server settings
76
+ server_name: Optional[str] = None
77
+ server_version: str = "1.0.0"
78
+ server_description: Optional[str] = None
79
+ server_icon: Optional[str] = None
80
+
81
+ # Session settings
82
+ session_ttl: int = 3600
83
+ max_concurrent_runs: int = 5
84
+
85
+ def __post_init__(self):
86
+ if self.server_name is None:
87
+ self.server_name = self.recipe_name
88
+ if self.tool_denylist is None:
89
+ self.tool_denylist = list(DEFAULT_DENIED_TOOLS)
90
+
91
+
92
+ @dataclass
93
+ class RecipeToolWrapper:
94
+ """Wrapper for a recipe tool with MCP metadata."""
95
+
96
+ name: str
97
+ description: str
98
+ handler: Callable
99
+ input_schema: Dict[str, Any]
100
+ output_schema: Optional[Dict[str, Any]] = None
101
+ annotations: Optional[Dict[str, Any]] = None
102
+ icon: Optional[str] = None
103
+ agent_name: Optional[str] = None
104
+ original_name: Optional[str] = None
105
+
106
+
107
+ class RecipeMCPAdapter:
108
+ """
109
+ Adapts a recipe to MCP server primitives.
110
+
111
+ Maps:
112
+ - Recipe metadata → Server metadata
113
+ - Agent tools → MCP Tools (namespaced)
114
+ - Agent instructions → MCP Prompts
115
+ - Recipe config/outputs → MCP Resources
116
+
117
+ Example:
118
+ adapter = RecipeMCPAdapter("support-reply")
119
+ adapter.load()
120
+ server = adapter.to_mcp_server()
121
+ server.run(transport="stdio")
122
+ """
123
+
124
+ def __init__(
125
+ self,
126
+ recipe_name: str,
127
+ config: Optional[RecipeMCPConfig] = None,
128
+ ):
129
+ """
130
+ Initialize recipe adapter.
131
+
132
+ Args:
133
+ recipe_name: Name of the recipe to adapt
134
+ config: Optional configuration (uses defaults if None)
135
+ """
136
+ self.recipe_name = recipe_name
137
+ self.config = config or RecipeMCPConfig(recipe_name=recipe_name)
138
+
139
+ self._recipe_config = None
140
+ self._workflow_config = None
141
+ self._loaded = False
142
+
143
+ # Registries
144
+ self._tool_registry = MCPToolRegistry()
145
+ self._resource_registry = MCPResourceRegistry()
146
+ self._prompt_registry = MCPPromptRegistry()
147
+
148
+ # Runtime state
149
+ self._active_runs: Dict[str, Any] = {}
150
+ self._run_results: Dict[str, Any] = {}
151
+
152
+ def load(self) -> None:
153
+ """Load recipe and build MCP registries."""
154
+ if self._loaded:
155
+ return
156
+
157
+ # Load recipe configuration
158
+ self._recipe_config = self._load_recipe_config()
159
+ if self._recipe_config is None:
160
+ raise ValueError(f"Recipe not found: {self.recipe_name}")
161
+
162
+ # Load workflow configuration
163
+ self._workflow_config = self._load_workflow_config()
164
+
165
+ # Build registries
166
+ self._build_tool_registry()
167
+ self._build_resource_registry()
168
+ self._build_prompt_registry()
169
+
170
+ self._loaded = True
171
+ logger.info(f"Loaded recipe '{self.recipe_name}' as MCP server")
172
+
173
+ def _load_recipe_config(self) -> Optional[Dict[str, Any]]:
174
+ """Load recipe configuration from template."""
175
+ try:
176
+ from ..recipe.core import _load_recipe
177
+ recipe = _load_recipe(self.recipe_name, offline=False)
178
+ if recipe:
179
+ return recipe.to_dict() if hasattr(recipe, 'to_dict') else recipe.raw
180
+ except ImportError:
181
+ pass
182
+
183
+ # Fallback: try loading directly from templates
184
+ try:
185
+ from ..templates.discovery import TemplateDiscovery
186
+ discovery = TemplateDiscovery()
187
+ template = discovery.find_template(self.recipe_name)
188
+ if template:
189
+ return self._load_yaml_file(template.path)
190
+ except ImportError:
191
+ pass
192
+
193
+ return None
194
+
195
+ def _load_workflow_config(self) -> Optional[Dict[str, Any]]:
196
+ """Load workflow configuration from recipe."""
197
+ if not self._recipe_config:
198
+ return None
199
+
200
+ # Check for workflow file reference
201
+ workflow_file = self._recipe_config.get("workflow")
202
+ if workflow_file:
203
+ recipe_path = self._recipe_config.get("path")
204
+ if recipe_path:
205
+ workflow_path = Path(recipe_path).parent / workflow_file
206
+ if workflow_path.exists():
207
+ return self._load_yaml_file(str(workflow_path))
208
+
209
+ # Check for inline workflow
210
+ if "agents" in self._recipe_config or "tasks" in self._recipe_config:
211
+ return self._recipe_config
212
+
213
+ return None
214
+
215
+ def _load_yaml_file(self, path: str) -> Optional[Dict[str, Any]]:
216
+ """Load YAML file."""
217
+ try:
218
+ import yaml
219
+ with open(path, 'r') as f:
220
+ return yaml.safe_load(f)
221
+ except Exception as e:
222
+ logger.warning(f"Failed to load YAML file {path}: {e}")
223
+ return None
224
+
225
+ def _build_tool_registry(self) -> None:
226
+ """Build tool registry from recipe."""
227
+ # Add recipe.run meta-tool
228
+ if self.config.expose_run_tool:
229
+ self._register_run_tool()
230
+
231
+ # Add agent tools (if enabled and safe)
232
+ if self.config.expose_agent_tools and self._workflow_config:
233
+ self._register_agent_tools()
234
+
235
+ def _register_run_tool(self) -> None:
236
+ """Register the recipe.run meta-tool."""
237
+ recipe_name = self.recipe_name
238
+ description = self._recipe_config.get("description", f"Execute the {recipe_name} recipe")
239
+
240
+ # Build input schema from recipe config_schema
241
+ config_schema = self._recipe_config.get("config_schema", {})
242
+ input_schema = {
243
+ "type": "object",
244
+ "properties": {
245
+ "input": {
246
+ "type": "object",
247
+ "description": "Input data for the recipe",
248
+ },
249
+ "config": {
250
+ "type": "object",
251
+ "description": "Configuration overrides",
252
+ "properties": config_schema.get("properties", {}),
253
+ },
254
+ "session_id": {
255
+ "type": "string",
256
+ "description": "Optional session ID for state grouping",
257
+ },
258
+ },
259
+ "required": [],
260
+ }
261
+
262
+ async def run_recipe_handler(
263
+ input: Optional[Dict[str, Any]] = None,
264
+ config: Optional[Dict[str, Any]] = None,
265
+ session_id: Optional[str] = None,
266
+ ) -> Dict[str, Any]:
267
+ """Execute the recipe."""
268
+ return await self._execute_recipe(input, config, session_id)
269
+
270
+ tool_name = self._namespace_tool(recipe_name, None, "run")
271
+
272
+ self._tool_registry.register(
273
+ name=tool_name,
274
+ handler=run_recipe_handler,
275
+ description=description,
276
+ input_schema=input_schema,
277
+ annotations={
278
+ "recipe": recipe_name,
279
+ "type": "recipe_run",
280
+ },
281
+ )
282
+
283
+ def _register_agent_tools(self) -> None:
284
+ """Register individual agent tools."""
285
+ agents = self._workflow_config.get("agents", [])
286
+
287
+ for agent_config in agents:
288
+ agent_name = agent_config.get("name", "agent")
289
+ agent_tools = agent_config.get("tools", [])
290
+
291
+ for tool_spec in agent_tools:
292
+ tool_name = tool_spec if isinstance(tool_spec, str) else tool_spec.get("name", "")
293
+
294
+ # Check if tool is allowed
295
+ if not self._is_tool_allowed(tool_name):
296
+ logger.debug(f"Skipping denied tool: {tool_name}")
297
+ continue
298
+
299
+ # Create wrapper for the tool
300
+ self._register_agent_tool(agent_name, tool_name, tool_spec)
301
+
302
+ def _register_agent_tool(
303
+ self,
304
+ agent_name: str,
305
+ tool_name: str,
306
+ tool_spec: Union[str, Dict[str, Any]],
307
+ ) -> None:
308
+ """Register a single agent tool."""
309
+ # Get tool description
310
+ if isinstance(tool_spec, dict):
311
+ description = tool_spec.get("description", f"Tool: {tool_name}")
312
+ input_schema = tool_spec.get("input_schema", {"type": "object", "properties": {}})
313
+ else:
314
+ description = f"Tool: {tool_name}"
315
+ input_schema = {"type": "object", "properties": {}}
316
+
317
+ # Create namespaced tool name
318
+ namespaced_name = self._namespace_tool(self.recipe_name, agent_name, tool_name)
319
+
320
+ async def tool_handler(**kwargs) -> Dict[str, Any]:
321
+ """Execute the agent tool."""
322
+ return await self._execute_agent_tool(agent_name, tool_name, kwargs)
323
+
324
+ self._tool_registry.register(
325
+ name=namespaced_name,
326
+ handler=tool_handler,
327
+ description=description,
328
+ input_schema=input_schema,
329
+ annotations={
330
+ "recipe": self.recipe_name,
331
+ "agent": agent_name,
332
+ "original_tool": tool_name,
333
+ "type": "agent_tool",
334
+ },
335
+ )
336
+
337
+ def _is_tool_allowed(self, tool_name: str) -> bool:
338
+ """Check if a tool is allowed based on config."""
339
+ if not self.config.safe_mode:
340
+ return True
341
+
342
+ # Check explicit allowlist
343
+ if self.config.tool_allowlist:
344
+ return tool_name in self.config.tool_allowlist
345
+
346
+ # Check denylist
347
+ if self.config.tool_denylist:
348
+ for denied in self.config.tool_denylist:
349
+ if denied in tool_name or tool_name in denied:
350
+ return False
351
+
352
+ return True
353
+
354
+ def _namespace_tool(
355
+ self,
356
+ recipe_name: str,
357
+ agent_name: Optional[str],
358
+ tool_name: str,
359
+ ) -> str:
360
+ """Generate namespaced tool name per MCP 2025-11-25 guidance."""
361
+ # Sanitize names
362
+ recipe = self._sanitize_name(recipe_name)
363
+ tool = self._sanitize_name(tool_name)
364
+
365
+ if self.config.tool_namespace == "flat":
366
+ return tool
367
+ elif self.config.tool_namespace == "nested":
368
+ if agent_name:
369
+ agent = self._sanitize_name(agent_name)
370
+ return f"{recipe}/{agent}/{tool}"
371
+ return f"{recipe}/{tool}"
372
+ else: # prefixed (default)
373
+ if agent_name:
374
+ agent = self._sanitize_name(agent_name)
375
+ return f"{recipe}.{agent}.{tool}"
376
+ return f"{recipe}.{tool}"
377
+
378
+ def _sanitize_name(self, name: str) -> str:
379
+ """Sanitize name for MCP tool naming."""
380
+ # Convert to lowercase, replace spaces/underscores with hyphens
381
+ name = name.lower().strip()
382
+ name = re.sub(r'[_\s]+', '-', name)
383
+ name = re.sub(r'[^a-z0-9\-]', '', name)
384
+ return name
385
+
386
+ def _build_resource_registry(self) -> None:
387
+ """Build resource registry from recipe."""
388
+ if not self._recipe_config:
389
+ return
390
+
391
+ recipe_name = self.recipe_name
392
+
393
+ # Register config resource
394
+ if self.config.expose_config:
395
+ self._resource_registry.register(
396
+ uri=f"recipe://{recipe_name}/config",
397
+ handler=lambda: self._recipe_config,
398
+ name=f"{recipe_name}-config",
399
+ description=f"Configuration for {recipe_name} recipe",
400
+ mime_type="application/json",
401
+ )
402
+
403
+ # Register schema resource
404
+ config_schema = self._recipe_config.get("config_schema")
405
+ if config_schema:
406
+ self._resource_registry.register(
407
+ uri=f"recipe://{recipe_name}/schema",
408
+ handler=lambda: config_schema,
409
+ name=f"{recipe_name}-schema",
410
+ description=f"Input schema for {recipe_name} recipe",
411
+ mime_type="application/json",
412
+ )
413
+
414
+ # Register outputs resource
415
+ if self.config.expose_outputs:
416
+ outputs = self._recipe_config.get("outputs", [])
417
+ if outputs:
418
+ self._resource_registry.register(
419
+ uri=f"recipe://{recipe_name}/outputs",
420
+ handler=lambda: outputs,
421
+ name=f"{recipe_name}-outputs",
422
+ description=f"Output definitions for {recipe_name} recipe",
423
+ mime_type="application/json",
424
+ )
425
+
426
+ def _build_prompt_registry(self) -> None:
427
+ """Build prompt registry from recipe."""
428
+ if not self.config.expose_prompts:
429
+ return
430
+
431
+ recipe_name = self.recipe_name
432
+
433
+ # Register recipe description as prompt
434
+ description = self._recipe_config.get("description", "")
435
+ if description:
436
+ self._prompt_registry.register(
437
+ name=f"{recipe_name}-description",
438
+ handler=lambda: description,
439
+ description=f"Description of {recipe_name} recipe",
440
+ )
441
+
442
+ # Register agent instructions as prompts
443
+ if self.config.expose_agent_instructions and self._workflow_config:
444
+ agents = self._workflow_config.get("agents", [])
445
+ for agent_config in agents:
446
+ agent_name = agent_config.get("name", "agent")
447
+ instructions = agent_config.get("instructions", agent_config.get("role", ""))
448
+
449
+ if instructions:
450
+ self._prompt_registry.register(
451
+ name=f"{recipe_name}-{self._sanitize_name(agent_name)}-instructions",
452
+ handler=lambda inst=instructions: inst,
453
+ description=f"Instructions for {agent_name} agent in {recipe_name}",
454
+ )
455
+
456
+ async def _execute_recipe(
457
+ self,
458
+ input: Optional[Dict[str, Any]] = None,
459
+ config: Optional[Dict[str, Any]] = None,
460
+ session_id: Optional[str] = None,
461
+ ) -> Dict[str, Any]:
462
+ """Execute the recipe."""
463
+ try:
464
+ from ..recipe.core import run as recipe_run
465
+
466
+ result = recipe_run(
467
+ name=self.recipe_name,
468
+ input=input or {},
469
+ config=config,
470
+ session_id=session_id,
471
+ )
472
+
473
+ return result.to_dict() if hasattr(result, 'to_dict') else {"output": result}
474
+
475
+ except ImportError:
476
+ return {"error": "Recipe execution not available", "status": "failed"}
477
+ except Exception as e:
478
+ logger.exception(f"Recipe execution failed: {e}")
479
+ return {"error": str(e), "status": "failed"}
480
+
481
+ async def _execute_agent_tool(
482
+ self,
483
+ agent_name: str,
484
+ tool_name: str,
485
+ arguments: Dict[str, Any],
486
+ ) -> Dict[str, Any]:
487
+ """Execute an agent tool directly."""
488
+ try:
489
+ # Try to load and execute the tool
490
+ from praisonaiagents.tools import get_tool
491
+
492
+ tool = get_tool(tool_name)
493
+ if tool:
494
+ if asyncio.iscoroutinefunction(tool):
495
+ result = await tool(**arguments)
496
+ else:
497
+ result = tool(**arguments)
498
+ return {"result": result, "status": "success"}
499
+
500
+ return {"error": f"Tool not found: {tool_name}", "status": "failed"}
501
+
502
+ except ImportError:
503
+ return {"error": "Tool execution not available", "status": "failed"}
504
+ except Exception as e:
505
+ logger.exception(f"Tool execution failed: {e}")
506
+ return {"error": str(e), "status": "failed"}
507
+
508
+ def to_mcp_server(self) -> "MCPServer":
509
+ """Create MCPServer instance from this adapter."""
510
+ if not self._loaded:
511
+ self.load()
512
+
513
+ from .server import MCPServer
514
+
515
+ return MCPServer(
516
+ name=self.config.server_name or self.recipe_name,
517
+ version=self.config.server_version,
518
+ tool_registry=self._tool_registry,
519
+ resource_registry=self._resource_registry,
520
+ prompt_registry=self._prompt_registry,
521
+ instructions=self._recipe_config.get("description", ""),
522
+ )
523
+
524
+ def get_tool_registry(self) -> MCPToolRegistry:
525
+ """Get the tool registry."""
526
+ return self._tool_registry
527
+
528
+ def get_resource_registry(self) -> MCPResourceRegistry:
529
+ """Get the resource registry."""
530
+ return self._resource_registry
531
+
532
+ def get_prompt_registry(self) -> MCPPromptRegistry:
533
+ """Get the prompt registry."""
534
+ return self._prompt_registry
535
+
536
+ def get_recipe_info(self) -> Dict[str, Any]:
537
+ """Get recipe information."""
538
+ if not self._loaded:
539
+ self.load()
540
+
541
+ return {
542
+ "name": self.recipe_name,
543
+ "version": self._recipe_config.get("version", "1.0.0"),
544
+ "description": self._recipe_config.get("description", ""),
545
+ "author": self._recipe_config.get("author"),
546
+ "tags": self._recipe_config.get("tags", []),
547
+ "tools_count": len(self._tool_registry.list_all()),
548
+ "resources_count": len(self._resource_registry.list_all()),
549
+ "prompts_count": len(self._prompt_registry.list_all()),
550
+ }
551
+
552
+
553
+ def create_recipe_mcp_server(
554
+ recipe_name: str,
555
+ config: Optional[RecipeMCPConfig] = None,
556
+ ) -> "MCPServer":
557
+ """
558
+ Create an MCP server from a recipe.
559
+
560
+ Args:
561
+ recipe_name: Name of the recipe
562
+ config: Optional configuration
563
+
564
+ Returns:
565
+ MCPServer instance ready to run
566
+
567
+ Example:
568
+ server = create_recipe_mcp_server("support-reply")
569
+ server.run(transport="stdio")
570
+ """
571
+ adapter = RecipeMCPAdapter(recipe_name, config)
572
+ adapter.load()
573
+ return adapter.to_mcp_server()