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,703 @@
1
+ """
2
+ MCP Tool/Resource/Prompt Registry
3
+
4
+ Provides a centralized registry for MCP tools, resources, and prompts.
5
+ Supports lazy registration, schema generation, pagination, and search.
6
+
7
+ MCP Protocol Version: 2025-11-25
8
+ """
9
+
10
+ import base64
11
+ import inspect
12
+ import logging
13
+ from dataclasses import dataclass, field
14
+ from typing import Any, Callable, Dict, List, Optional, Tuple, Union
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ # Default page size for pagination
20
+ DEFAULT_PAGE_SIZE = 50
21
+ MAX_PAGE_SIZE = 100
22
+
23
+
24
+ def encode_cursor(offset: int, snapshot_hash: Optional[str] = None) -> str:
25
+ """Encode pagination cursor as base64url."""
26
+ data = f"{offset}"
27
+ if snapshot_hash:
28
+ data = f"{offset}:{snapshot_hash}"
29
+ return base64.urlsafe_b64encode(data.encode()).decode().rstrip("=")
30
+
31
+
32
+ def decode_cursor(cursor: str) -> Tuple[int, Optional[str]]:
33
+ """
34
+ Decode pagination cursor from base64url.
35
+
36
+ Returns:
37
+ Tuple of (offset, snapshot_hash)
38
+
39
+ Raises:
40
+ ValueError: If cursor is invalid
41
+ """
42
+ try:
43
+ # Add padding if needed
44
+ padding = 4 - len(cursor) % 4
45
+ if padding != 4:
46
+ cursor += "=" * padding
47
+ data = base64.urlsafe_b64decode(cursor).decode()
48
+ if ":" in data:
49
+ parts = data.split(":", 1)
50
+ return int(parts[0]), parts[1]
51
+ return int(data), None
52
+ except Exception as e:
53
+ raise ValueError(f"Invalid cursor: {e}")
54
+
55
+
56
+ @dataclass
57
+ class MCPToolDefinition:
58
+ """Definition of an MCP tool with MCP 2025-11-25 annotations."""
59
+ name: str
60
+ description: str
61
+ handler: Callable
62
+ input_schema: Dict[str, Any]
63
+ output_schema: Optional[Dict[str, Any]] = None
64
+ annotations: Optional[Dict[str, Any]] = None
65
+ # MCP 2025-11-25 tool annotation hints
66
+ title: Optional[str] = None
67
+ category: Optional[str] = None
68
+ tags: Optional[List[str]] = None
69
+ # Behavior hints per MCP 2025-11-25 spec
70
+ read_only_hint: bool = False
71
+ destructive_hint: bool = True
72
+ idempotent_hint: bool = False
73
+ open_world_hint: bool = True
74
+
75
+ def to_mcp_schema(self) -> Dict[str, Any]:
76
+ """Convert to MCP tool schema format per MCP 2025-11-25 spec."""
77
+ schema = {
78
+ "name": self.name,
79
+ "description": self.description,
80
+ "inputSchema": self.input_schema,
81
+ }
82
+ if self.output_schema:
83
+ schema["outputSchema"] = self.output_schema
84
+
85
+ # Build annotations per MCP 2025-11-25 spec
86
+ annotations = self.annotations.copy() if self.annotations else {}
87
+
88
+ # Add behavior hints
89
+ annotations["readOnlyHint"] = self.read_only_hint
90
+ annotations["destructiveHint"] = self.destructive_hint
91
+ annotations["idempotentHint"] = self.idempotent_hint
92
+ annotations["openWorldHint"] = self.open_world_hint
93
+
94
+ # Add title if present
95
+ if self.title:
96
+ annotations["title"] = self.title
97
+
98
+ schema["annotations"] = annotations
99
+ return schema
100
+
101
+
102
+ @dataclass
103
+ class MCPResourceDefinition:
104
+ """Definition of an MCP resource."""
105
+ uri: str
106
+ name: str
107
+ description: str
108
+ handler: Callable
109
+ mime_type: str = "application/json"
110
+
111
+ def to_mcp_schema(self) -> Dict[str, Any]:
112
+ """Convert to MCP resource schema format."""
113
+ return {
114
+ "uri": self.uri,
115
+ "name": self.name,
116
+ "description": self.description,
117
+ "mimeType": self.mime_type,
118
+ }
119
+
120
+
121
+ @dataclass
122
+ class MCPPromptDefinition:
123
+ """Definition of an MCP prompt."""
124
+ name: str
125
+ description: str
126
+ handler: Callable
127
+ arguments: List[Dict[str, Any]] = field(default_factory=list)
128
+
129
+ def to_mcp_schema(self) -> Dict[str, Any]:
130
+ """Convert to MCP prompt schema format."""
131
+ return {
132
+ "name": self.name,
133
+ "description": self.description,
134
+ "arguments": self.arguments,
135
+ }
136
+
137
+
138
+ class MCPToolRegistry:
139
+ """Registry for MCP tools."""
140
+
141
+ def __init__(self):
142
+ self._tools: Dict[str, MCPToolDefinition] = {}
143
+ self._lazy_loaders: List[Callable[[], None]] = []
144
+
145
+ def register(
146
+ self,
147
+ name: str,
148
+ handler: Callable,
149
+ description: Optional[str] = None,
150
+ input_schema: Optional[Dict[str, Any]] = None,
151
+ output_schema: Optional[Dict[str, Any]] = None,
152
+ annotations: Optional[Dict[str, Any]] = None,
153
+ ) -> None:
154
+ """Register a tool."""
155
+ if description is None:
156
+ description = inspect.getdoc(handler) or f"Tool: {name}"
157
+ if "\n" in description:
158
+ description = description.split("\n")[0]
159
+
160
+ if input_schema is None:
161
+ input_schema = self._generate_input_schema(handler)
162
+
163
+ self._tools[name] = MCPToolDefinition(
164
+ name=name,
165
+ description=description,
166
+ handler=handler,
167
+ input_schema=input_schema,
168
+ output_schema=output_schema,
169
+ annotations=annotations,
170
+ )
171
+ logger.debug(f"Registered MCP tool: {name}")
172
+
173
+ def register_lazy(self, loader: Callable[[], None]) -> None:
174
+ """Register a lazy loader that will be called before listing tools."""
175
+ self._lazy_loaders.append(loader)
176
+
177
+ def _ensure_loaded(self) -> None:
178
+ """Ensure all lazy loaders have been called."""
179
+ for loader in self._lazy_loaders:
180
+ try:
181
+ loader()
182
+ except Exception as e:
183
+ logger.warning(f"Lazy loader failed: {e}")
184
+ self._lazy_loaders.clear()
185
+
186
+ def get(self, name: str) -> Optional[MCPToolDefinition]:
187
+ """Get a tool by name."""
188
+ self._ensure_loaded()
189
+ return self._tools.get(name)
190
+
191
+ def list_all(self) -> List[MCPToolDefinition]:
192
+ """List all registered tools."""
193
+ self._ensure_loaded()
194
+ return list(self._tools.values())
195
+
196
+ def list_schemas(self) -> List[Dict[str, Any]]:
197
+ """List all tool schemas."""
198
+ return [tool.to_mcp_schema() for tool in self.list_all()]
199
+
200
+ def list_paginated(
201
+ self,
202
+ cursor: Optional[str] = None,
203
+ page_size: int = DEFAULT_PAGE_SIZE,
204
+ ) -> Tuple[List[Dict[str, Any]], Optional[str]]:
205
+ """
206
+ List tool schemas with pagination per MCP 2025-11-25 spec.
207
+
208
+ Args:
209
+ cursor: Opaque cursor from previous response
210
+ page_size: Number of items per page (server-determined, max 100)
211
+
212
+ Returns:
213
+ Tuple of (tools list, nextCursor or None if no more results)
214
+
215
+ Raises:
216
+ ValueError: If cursor is invalid
217
+ """
218
+ all_tools = self.list_all()
219
+ total = len(all_tools)
220
+
221
+ # Decode cursor to get offset
222
+ offset = 0
223
+ if cursor:
224
+ offset, _ = decode_cursor(cursor)
225
+ if offset < 0 or offset >= total:
226
+ raise ValueError(f"Invalid cursor: offset {offset} out of range")
227
+
228
+ # Clamp page size
229
+ page_size = min(page_size, MAX_PAGE_SIZE)
230
+
231
+ # Get page
232
+ end = min(offset + page_size, total)
233
+ page = all_tools[offset:end]
234
+ schemas = [tool.to_mcp_schema() for tool in page]
235
+
236
+ # Generate next cursor if more results
237
+ next_cursor = None
238
+ if end < total:
239
+ next_cursor = encode_cursor(end)
240
+
241
+ return schemas, next_cursor
242
+
243
+ def search(
244
+ self,
245
+ query: Optional[str] = None,
246
+ category: Optional[str] = None,
247
+ tags: Optional[List[str]] = None,
248
+ read_only: Optional[bool] = None,
249
+ cursor: Optional[str] = None,
250
+ page_size: int = DEFAULT_PAGE_SIZE,
251
+ ) -> Tuple[List[Dict[str, Any]], Optional[str], int]:
252
+ """
253
+ Search tools with filtering and pagination.
254
+
255
+ Args:
256
+ query: Text to search in name, description, tags
257
+ category: Filter by category
258
+ tags: Filter by tags (any match)
259
+ read_only: Filter by readOnlyHint
260
+ cursor: Pagination cursor
261
+ page_size: Items per page
262
+
263
+ Returns:
264
+ Tuple of (tools list, nextCursor, total_count)
265
+ """
266
+ all_tools = self.list_all()
267
+
268
+ # Apply filters
269
+ filtered = []
270
+ query_lower = query.lower() if query else None
271
+
272
+ for tool in all_tools:
273
+ # Query filter
274
+ if query_lower:
275
+ searchable = f"{tool.name} {tool.description}".lower()
276
+ if tool.tags:
277
+ searchable += " " + " ".join(tool.tags).lower()
278
+ if tool.category:
279
+ searchable += " " + tool.category.lower()
280
+ if query_lower not in searchable:
281
+ continue
282
+
283
+ # Category filter
284
+ if category and tool.category != category:
285
+ continue
286
+
287
+ # Tags filter (any match)
288
+ if tags:
289
+ tool_tags = set(tool.tags or [])
290
+ if not tool_tags.intersection(set(tags)):
291
+ continue
292
+
293
+ # Read-only filter
294
+ if read_only is not None and tool.read_only_hint != read_only:
295
+ continue
296
+
297
+ filtered.append(tool)
298
+
299
+ total = len(filtered)
300
+
301
+ # Pagination
302
+ offset = 0
303
+ if cursor:
304
+ offset, _ = decode_cursor(cursor)
305
+ if offset < 0:
306
+ offset = 0
307
+
308
+ page_size = min(page_size, MAX_PAGE_SIZE)
309
+ end = min(offset + page_size, total)
310
+ page = filtered[offset:end]
311
+ schemas = [tool.to_mcp_schema() for tool in page]
312
+
313
+ # Next cursor
314
+ next_cursor = None
315
+ if end < total:
316
+ next_cursor = encode_cursor(end)
317
+
318
+ return schemas, next_cursor, total
319
+
320
+ def _generate_input_schema(self, handler: Callable) -> Dict[str, Any]:
321
+ """Generate JSON schema from function signature."""
322
+ sig = inspect.signature(handler)
323
+ hints = getattr(handler, "__annotations__", {})
324
+
325
+ properties = {}
326
+ required = []
327
+
328
+ for param_name, param in sig.parameters.items():
329
+ if param_name in ("self", "cls"):
330
+ continue
331
+
332
+ prop = {"type": "string"} # Default
333
+
334
+ # Get type hint
335
+ hint = hints.get(param_name)
336
+ if hint:
337
+ prop = self._type_to_json_schema(hint)
338
+
339
+ # Get description from docstring if available
340
+ docstring = inspect.getdoc(handler)
341
+ if docstring:
342
+ # Try to extract param description from docstring
343
+ for line in docstring.split("\n"):
344
+ if f":param {param_name}:" in line:
345
+ # Extract description after the param marker
346
+ parts = line.split(f":param {param_name}:", 1)
347
+ if len(parts) > 1:
348
+ prop["description"] = parts[1].strip()
349
+
350
+ properties[param_name] = prop
351
+
352
+ if param.default is inspect.Parameter.empty:
353
+ required.append(param_name)
354
+
355
+ return {
356
+ "type": "object",
357
+ "properties": properties,
358
+ "required": required if required else None,
359
+ }
360
+
361
+ def _type_to_json_schema(self, hint: Any) -> Dict[str, Any]:
362
+ """Convert Python type hint to JSON schema."""
363
+ if hint is str:
364
+ return {"type": "string"}
365
+ elif hint is int:
366
+ return {"type": "integer"}
367
+ elif hint is float:
368
+ return {"type": "number"}
369
+ elif hint is bool:
370
+ return {"type": "boolean"}
371
+ elif hint is list or (hasattr(hint, "__origin__") and hint.__origin__ is list):
372
+ return {"type": "array", "items": {"type": "string"}}
373
+ elif hint is dict or (hasattr(hint, "__origin__") and hint.__origin__ is dict):
374
+ return {"type": "object"}
375
+ else:
376
+ return {"type": "string"}
377
+
378
+
379
+ class MCPResourceRegistry:
380
+ """Registry for MCP resources."""
381
+
382
+ def __init__(self):
383
+ self._resources: Dict[str, MCPResourceDefinition] = {}
384
+ self._lazy_loaders: List[Callable[[], None]] = []
385
+
386
+ def register(
387
+ self,
388
+ uri: str,
389
+ handler: Callable,
390
+ name: Optional[str] = None,
391
+ description: Optional[str] = None,
392
+ mime_type: str = "application/json",
393
+ ) -> None:
394
+ """Register a resource."""
395
+ if name is None:
396
+ name = uri.split("/")[-1] or uri
397
+ if description is None:
398
+ description = inspect.getdoc(handler) or f"Resource: {name}"
399
+ if "\n" in description:
400
+ description = description.split("\n")[0]
401
+
402
+ self._resources[uri] = MCPResourceDefinition(
403
+ uri=uri,
404
+ name=name,
405
+ description=description,
406
+ handler=handler,
407
+ mime_type=mime_type,
408
+ )
409
+ logger.debug(f"Registered MCP resource: {uri}")
410
+
411
+ def register_lazy(self, loader: Callable[[], None]) -> None:
412
+ """Register a lazy loader."""
413
+ self._lazy_loaders.append(loader)
414
+
415
+ def _ensure_loaded(self) -> None:
416
+ """Ensure all lazy loaders have been called."""
417
+ for loader in self._lazy_loaders:
418
+ try:
419
+ loader()
420
+ except Exception as e:
421
+ logger.warning(f"Lazy loader failed: {e}")
422
+ self._lazy_loaders.clear()
423
+
424
+ def get(self, uri: str) -> Optional[MCPResourceDefinition]:
425
+ """Get a resource by URI."""
426
+ self._ensure_loaded()
427
+ return self._resources.get(uri)
428
+
429
+ def list_all(self) -> List[MCPResourceDefinition]:
430
+ """List all registered resources."""
431
+ self._ensure_loaded()
432
+ return list(self._resources.values())
433
+
434
+ def list_schemas(self) -> List[Dict[str, Any]]:
435
+ """List all resource schemas."""
436
+ return [res.to_mcp_schema() for res in self.list_all()]
437
+
438
+ def list_paginated(
439
+ self,
440
+ cursor: Optional[str] = None,
441
+ page_size: int = DEFAULT_PAGE_SIZE,
442
+ ) -> Tuple[List[Dict[str, Any]], Optional[str]]:
443
+ """
444
+ List resource schemas with pagination per MCP 2025-11-25 spec.
445
+
446
+ Args:
447
+ cursor: Opaque cursor from previous response
448
+ page_size: Number of items per page
449
+
450
+ Returns:
451
+ Tuple of (resources list, nextCursor or None)
452
+ """
453
+ all_resources = self.list_all()
454
+ total = len(all_resources)
455
+
456
+ offset = 0
457
+ if cursor:
458
+ offset, _ = decode_cursor(cursor)
459
+ if offset < 0 or offset >= total:
460
+ raise ValueError(f"Invalid cursor: offset {offset} out of range")
461
+
462
+ page_size = min(page_size, MAX_PAGE_SIZE)
463
+ end = min(offset + page_size, total)
464
+ page = all_resources[offset:end]
465
+ schemas = [res.to_mcp_schema() for res in page]
466
+
467
+ next_cursor = None
468
+ if end < total:
469
+ next_cursor = encode_cursor(end)
470
+
471
+ return schemas, next_cursor
472
+
473
+
474
+ class MCPPromptRegistry:
475
+ """Registry for MCP prompts."""
476
+
477
+ def __init__(self):
478
+ self._prompts: Dict[str, MCPPromptDefinition] = {}
479
+ self._lazy_loaders: List[Callable[[], None]] = []
480
+
481
+ def register(
482
+ self,
483
+ name: str,
484
+ handler: Callable,
485
+ description: Optional[str] = None,
486
+ arguments: Optional[List[Dict[str, Any]]] = None,
487
+ ) -> None:
488
+ """Register a prompt."""
489
+ if description is None:
490
+ description = inspect.getdoc(handler) or f"Prompt: {name}"
491
+ if "\n" in description:
492
+ description = description.split("\n")[0]
493
+
494
+ if arguments is None:
495
+ arguments = self._generate_arguments(handler)
496
+
497
+ self._prompts[name] = MCPPromptDefinition(
498
+ name=name,
499
+ description=description,
500
+ handler=handler,
501
+ arguments=arguments,
502
+ )
503
+ logger.debug(f"Registered MCP prompt: {name}")
504
+
505
+ def register_lazy(self, loader: Callable[[], None]) -> None:
506
+ """Register a lazy loader."""
507
+ self._lazy_loaders.append(loader)
508
+
509
+ def _ensure_loaded(self) -> None:
510
+ """Ensure all lazy loaders have been called."""
511
+ for loader in self._lazy_loaders:
512
+ try:
513
+ loader()
514
+ except Exception as e:
515
+ logger.warning(f"Lazy loader failed: {e}")
516
+ self._lazy_loaders.clear()
517
+
518
+ def get(self, name: str) -> Optional[MCPPromptDefinition]:
519
+ """Get a prompt by name."""
520
+ self._ensure_loaded()
521
+ return self._prompts.get(name)
522
+
523
+ def list_all(self) -> List[MCPPromptDefinition]:
524
+ """List all registered prompts."""
525
+ self._ensure_loaded()
526
+ return list(self._prompts.values())
527
+
528
+ def list_schemas(self) -> List[Dict[str, Any]]:
529
+ """List all prompt schemas."""
530
+ return [prompt.to_mcp_schema() for prompt in self.list_all()]
531
+
532
+ def list_paginated(
533
+ self,
534
+ cursor: Optional[str] = None,
535
+ page_size: int = DEFAULT_PAGE_SIZE,
536
+ ) -> Tuple[List[Dict[str, Any]], Optional[str]]:
537
+ """
538
+ List prompt schemas with pagination per MCP 2025-11-25 spec.
539
+
540
+ Args:
541
+ cursor: Opaque cursor from previous response
542
+ page_size: Number of items per page
543
+
544
+ Returns:
545
+ Tuple of (prompts list, nextCursor or None)
546
+ """
547
+ all_prompts = self.list_all()
548
+ total = len(all_prompts)
549
+
550
+ offset = 0
551
+ if cursor:
552
+ offset, _ = decode_cursor(cursor)
553
+ if offset < 0 or offset >= total:
554
+ raise ValueError(f"Invalid cursor: offset {offset} out of range")
555
+
556
+ page_size = min(page_size, MAX_PAGE_SIZE)
557
+ end = min(offset + page_size, total)
558
+ page = all_prompts[offset:end]
559
+ schemas = [prompt.to_mcp_schema() for prompt in page]
560
+
561
+ next_cursor = None
562
+ if end < total:
563
+ next_cursor = encode_cursor(end)
564
+
565
+ return schemas, next_cursor
566
+
567
+ def _generate_arguments(self, handler: Callable) -> List[Dict[str, Any]]:
568
+ """Generate prompt arguments from function signature."""
569
+ sig = inspect.signature(handler)
570
+ arguments = []
571
+
572
+ for param_name, param in sig.parameters.items():
573
+ if param_name in ("self", "cls"):
574
+ continue
575
+
576
+ arg = {
577
+ "name": param_name,
578
+ "required": param.default is inspect.Parameter.empty,
579
+ }
580
+
581
+ # Add description if available
582
+ docstring = inspect.getdoc(handler)
583
+ if docstring and f":param {param_name}:" in docstring:
584
+ # Extract description
585
+ pass
586
+
587
+ arguments.append(arg)
588
+
589
+ return arguments
590
+
591
+
592
+ # Global registries
593
+ _tool_registry = MCPToolRegistry()
594
+ _resource_registry = MCPResourceRegistry()
595
+ _prompt_registry = MCPPromptRegistry()
596
+
597
+
598
+ def register_tool(
599
+ name: str,
600
+ handler: Optional[Callable] = None,
601
+ description: Optional[str] = None,
602
+ input_schema: Optional[Dict[str, Any]] = None,
603
+ output_schema: Optional[Dict[str, Any]] = None,
604
+ annotations: Optional[Dict[str, Any]] = None,
605
+ ) -> Union[Callable, None]:
606
+ """
607
+ Register a tool with the global registry.
608
+
609
+ Can be used as a decorator or called directly.
610
+
611
+ Example:
612
+ @register_tool("praisonai.memory.show")
613
+ def show_memory(session_id: str = None) -> str:
614
+ '''Show memory contents.'''
615
+ ...
616
+
617
+ # Or directly:
618
+ register_tool("praisonai.memory.show", show_memory)
619
+ """
620
+ def decorator(fn: Callable) -> Callable:
621
+ _tool_registry.register(
622
+ name=name,
623
+ handler=fn,
624
+ description=description,
625
+ input_schema=input_schema,
626
+ output_schema=output_schema,
627
+ annotations=annotations,
628
+ )
629
+ return fn
630
+
631
+ if handler is not None:
632
+ decorator(handler)
633
+ return None
634
+ return decorator
635
+
636
+
637
+ def register_resource(
638
+ uri: str,
639
+ handler: Optional[Callable] = None,
640
+ name: Optional[str] = None,
641
+ description: Optional[str] = None,
642
+ mime_type: str = "application/json",
643
+ ) -> Union[Callable, None]:
644
+ """
645
+ Register a resource with the global registry.
646
+
647
+ Can be used as a decorator or called directly.
648
+ """
649
+ def decorator(fn: Callable) -> Callable:
650
+ _resource_registry.register(
651
+ uri=uri,
652
+ handler=fn,
653
+ name=name,
654
+ description=description,
655
+ mime_type=mime_type,
656
+ )
657
+ return fn
658
+
659
+ if handler is not None:
660
+ decorator(handler)
661
+ return None
662
+ return decorator
663
+
664
+
665
+ def register_prompt(
666
+ name: str,
667
+ handler: Optional[Callable] = None,
668
+ description: Optional[str] = None,
669
+ arguments: Optional[List[Dict[str, Any]]] = None,
670
+ ) -> Union[Callable, None]:
671
+ """
672
+ Register a prompt with the global registry.
673
+
674
+ Can be used as a decorator or called directly.
675
+ """
676
+ def decorator(fn: Callable) -> Callable:
677
+ _prompt_registry.register(
678
+ name=name,
679
+ handler=fn,
680
+ description=description,
681
+ arguments=arguments,
682
+ )
683
+ return fn
684
+
685
+ if handler is not None:
686
+ decorator(handler)
687
+ return None
688
+ return decorator
689
+
690
+
691
+ def get_tool_registry() -> MCPToolRegistry:
692
+ """Get the global tool registry."""
693
+ return _tool_registry
694
+
695
+
696
+ def get_resource_registry() -> MCPResourceRegistry:
697
+ """Get the global resource registry."""
698
+ return _resource_registry
699
+
700
+
701
+ def get_prompt_registry() -> MCPPromptRegistry:
702
+ """Get the global prompt registry."""
703
+ return _prompt_registry