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,460 @@
1
+ """
2
+ OAuth 2.1 Implementation for MCP
3
+
4
+ Implements OAuth 2.1 authorization framework per MCP 2025-11-25 specification.
5
+
6
+ Based on:
7
+ - OAuth 2.1 IETF DRAFT (draft-ietf-oauth-v2-1-13)
8
+ - OAuth 2.0 Authorization Server Metadata (RFC8414)
9
+ - OAuth 2.0 Dynamic Client Registration Protocol (RFC7591)
10
+ - OAuth 2.0 Protected Resource Metadata (RFC9728)
11
+ """
12
+
13
+ import base64
14
+ import hashlib
15
+ import logging
16
+ import secrets
17
+ import time
18
+ from dataclasses import dataclass, field
19
+ from typing import Any, Callable, Dict, List, Optional
20
+ from urllib.parse import urlencode
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ @dataclass
26
+ class OAuthConfig:
27
+ """OAuth 2.1 configuration for MCP servers."""
28
+
29
+ # Authorization server endpoints
30
+ authorization_endpoint: str
31
+ token_endpoint: str
32
+
33
+ # Client credentials
34
+ client_id: str
35
+ client_secret: Optional[str] = None
36
+
37
+ # Scopes
38
+ scopes: List[str] = field(default_factory=list)
39
+ default_scopes: List[str] = field(default_factory=list)
40
+
41
+ # PKCE settings (required for OAuth 2.1)
42
+ use_pkce: bool = True
43
+ pkce_method: str = "S256" # S256 or plain
44
+
45
+ # Token settings
46
+ token_endpoint_auth_method: str = "client_secret_basic"
47
+
48
+ # Discovery
49
+ issuer: Optional[str] = None
50
+ metadata_url: Optional[str] = None
51
+
52
+ # Resource indicator (RFC 8707)
53
+ resource_indicator: Optional[str] = None
54
+
55
+ # Redirect settings
56
+ redirect_uri: Optional[str] = None
57
+
58
+ def to_dict(self) -> Dict[str, Any]:
59
+ return {
60
+ "authorization_endpoint": self.authorization_endpoint,
61
+ "token_endpoint": self.token_endpoint,
62
+ "client_id": self.client_id,
63
+ "scopes": self.scopes,
64
+ "use_pkce": self.use_pkce,
65
+ "issuer": self.issuer,
66
+ }
67
+
68
+
69
+ @dataclass
70
+ class TokenResponse:
71
+ """OAuth token response."""
72
+ access_token: str
73
+ token_type: str = "Bearer"
74
+ expires_in: Optional[int] = None
75
+ refresh_token: Optional[str] = None
76
+ scope: Optional[str] = None
77
+ id_token: Optional[str] = None
78
+
79
+ # Computed fields
80
+ expires_at: Optional[float] = None
81
+
82
+ def __post_init__(self):
83
+ if self.expires_in and not self.expires_at:
84
+ self.expires_at = time.time() + self.expires_in
85
+
86
+ def is_expired(self) -> bool:
87
+ if self.expires_at is None:
88
+ return False
89
+ return time.time() >= self.expires_at
90
+
91
+ def to_dict(self) -> Dict[str, Any]:
92
+ result = {
93
+ "access_token": self.access_token,
94
+ "token_type": self.token_type,
95
+ }
96
+ if self.expires_in:
97
+ result["expires_in"] = self.expires_in
98
+ if self.refresh_token:
99
+ result["refresh_token"] = self.refresh_token
100
+ if self.scope:
101
+ result["scope"] = self.scope
102
+ return result
103
+
104
+
105
+ @dataclass
106
+ class AuthorizationRequest:
107
+ """OAuth authorization request state."""
108
+ state: str
109
+ code_verifier: Optional[str] = None
110
+ code_challenge: Optional[str] = None
111
+ scopes: List[str] = field(default_factory=list)
112
+ redirect_uri: Optional[str] = None
113
+ created_at: float = field(default_factory=time.time)
114
+
115
+ def is_expired(self, ttl: int = 600) -> bool:
116
+ return time.time() - self.created_at > ttl
117
+
118
+
119
+ class OAuthManager:
120
+ """
121
+ OAuth 2.1 Manager for MCP servers.
122
+
123
+ Handles:
124
+ - Authorization code flow with PKCE
125
+ - Token exchange and refresh
126
+ - Incremental scope consent
127
+ - WWW-Authenticate challenges
128
+ """
129
+
130
+ def __init__(
131
+ self,
132
+ config: OAuthConfig,
133
+ token_store: Optional[Callable] = None,
134
+ ):
135
+ """
136
+ Initialize OAuth manager.
137
+
138
+ Args:
139
+ config: OAuth configuration
140
+ token_store: Optional token storage callback
141
+ """
142
+ self.config = config
143
+ self._token_store = token_store
144
+ self._pending_requests: Dict[str, AuthorizationRequest] = {}
145
+ self._tokens: Dict[str, TokenResponse] = {}
146
+
147
+ def create_authorization_url(
148
+ self,
149
+ scopes: Optional[List[str]] = None,
150
+ state: Optional[str] = None,
151
+ redirect_uri: Optional[str] = None,
152
+ ) -> tuple[str, AuthorizationRequest]:
153
+ """
154
+ Create authorization URL for OAuth flow.
155
+
156
+ Args:
157
+ scopes: Requested scopes
158
+ state: Optional state parameter
159
+ redirect_uri: Redirect URI
160
+
161
+ Returns:
162
+ Tuple of (authorization_url, request_state)
163
+ """
164
+ # Generate state if not provided
165
+ state = state or secrets.token_urlsafe(32)
166
+
167
+ # Use default scopes if not specified
168
+ scopes = scopes or self.config.default_scopes
169
+
170
+ # Generate PKCE parameters
171
+ code_verifier = None
172
+ code_challenge = None
173
+
174
+ if self.config.use_pkce:
175
+ code_verifier = secrets.token_urlsafe(64)
176
+ if self.config.pkce_method == "S256":
177
+ code_challenge = base64.urlsafe_b64encode(
178
+ hashlib.sha256(code_verifier.encode()).digest()
179
+ ).decode().rstrip("=")
180
+ else:
181
+ code_challenge = code_verifier
182
+
183
+ # Build authorization URL
184
+ params = {
185
+ "response_type": "code",
186
+ "client_id": self.config.client_id,
187
+ "state": state,
188
+ "scope": " ".join(scopes),
189
+ }
190
+
191
+ if redirect_uri or self.config.redirect_uri:
192
+ params["redirect_uri"] = redirect_uri or self.config.redirect_uri
193
+
194
+ if code_challenge:
195
+ params["code_challenge"] = code_challenge
196
+ params["code_challenge_method"] = self.config.pkce_method
197
+
198
+ if self.config.resource_indicator:
199
+ params["resource"] = self.config.resource_indicator
200
+
201
+ auth_url = f"{self.config.authorization_endpoint}?{urlencode(params)}"
202
+
203
+ # Store request state
204
+ request = AuthorizationRequest(
205
+ state=state,
206
+ code_verifier=code_verifier,
207
+ code_challenge=code_challenge,
208
+ scopes=scopes,
209
+ redirect_uri=redirect_uri or self.config.redirect_uri,
210
+ )
211
+ self._pending_requests[state] = request
212
+
213
+ return auth_url, request
214
+
215
+ async def exchange_code(
216
+ self,
217
+ code: str,
218
+ state: str,
219
+ ) -> TokenResponse:
220
+ """
221
+ Exchange authorization code for tokens.
222
+
223
+ Args:
224
+ code: Authorization code
225
+ state: State parameter
226
+
227
+ Returns:
228
+ Token response
229
+ """
230
+ # Validate state
231
+ request = self._pending_requests.get(state)
232
+ if not request:
233
+ raise ValueError("Invalid state parameter")
234
+
235
+ if request.is_expired():
236
+ del self._pending_requests[state]
237
+ raise ValueError("Authorization request expired")
238
+
239
+ # Build token request
240
+ data = {
241
+ "grant_type": "authorization_code",
242
+ "code": code,
243
+ "client_id": self.config.client_id,
244
+ }
245
+
246
+ if request.redirect_uri:
247
+ data["redirect_uri"] = request.redirect_uri
248
+
249
+ if request.code_verifier:
250
+ data["code_verifier"] = request.code_verifier
251
+
252
+ # Make token request
253
+ token_response = await self._token_request(data)
254
+
255
+ # Cleanup
256
+ del self._pending_requests[state]
257
+
258
+ return token_response
259
+
260
+ async def refresh_token(
261
+ self,
262
+ refresh_token: str,
263
+ scopes: Optional[List[str]] = None,
264
+ ) -> TokenResponse:
265
+ """
266
+ Refresh an access token.
267
+
268
+ Args:
269
+ refresh_token: Refresh token
270
+ scopes: Optional scope restriction
271
+
272
+ Returns:
273
+ New token response
274
+ """
275
+ data = {
276
+ "grant_type": "refresh_token",
277
+ "refresh_token": refresh_token,
278
+ "client_id": self.config.client_id,
279
+ }
280
+
281
+ if scopes:
282
+ data["scope"] = " ".join(scopes)
283
+
284
+ return await self._token_request(data)
285
+
286
+ async def _token_request(self, data: Dict[str, str]) -> TokenResponse:
287
+ """Make a token request."""
288
+ try:
289
+ import httpx
290
+ except ImportError:
291
+ raise ImportError("httpx required for OAuth. Install with: pip install httpx")
292
+
293
+ headers = {"Content-Type": "application/x-www-form-urlencoded"}
294
+
295
+ # Add client authentication
296
+ if self.config.token_endpoint_auth_method == "client_secret_basic":
297
+ if self.config.client_secret:
298
+ credentials = base64.b64encode(
299
+ f"{self.config.client_id}:{self.config.client_secret}".encode()
300
+ ).decode()
301
+ headers["Authorization"] = f"Basic {credentials}"
302
+ elif self.config.token_endpoint_auth_method == "client_secret_post":
303
+ if self.config.client_secret:
304
+ data["client_secret"] = self.config.client_secret
305
+
306
+ async with httpx.AsyncClient() as client:
307
+ response = await client.post(
308
+ self.config.token_endpoint,
309
+ data=data,
310
+ headers=headers,
311
+ )
312
+ response.raise_for_status()
313
+
314
+ token_data = response.json()
315
+ return TokenResponse(
316
+ access_token=token_data["access_token"],
317
+ token_type=token_data.get("token_type", "Bearer"),
318
+ expires_in=token_data.get("expires_in"),
319
+ refresh_token=token_data.get("refresh_token"),
320
+ scope=token_data.get("scope"),
321
+ id_token=token_data.get("id_token"),
322
+ )
323
+
324
+ def create_www_authenticate_challenge(
325
+ self,
326
+ required_scopes: List[str],
327
+ error: Optional[str] = None,
328
+ error_description: Optional[str] = None,
329
+ ) -> str:
330
+ """
331
+ Create WWW-Authenticate header for scope challenge.
332
+
333
+ Per MCP 2025-11-25, this enables incremental scope consent.
334
+
335
+ Args:
336
+ required_scopes: Scopes required for the operation
337
+ error: OAuth error code
338
+ error_description: Human-readable error description
339
+
340
+ Returns:
341
+ WWW-Authenticate header value
342
+ """
343
+ parts = ['Bearer']
344
+
345
+ params = []
346
+
347
+ if self.config.issuer:
348
+ params.append(f'realm="{self.config.issuer}"')
349
+
350
+ if required_scopes:
351
+ params.append(f'scope="{" ".join(required_scopes)}"')
352
+
353
+ if error:
354
+ params.append(f'error="{error}"')
355
+
356
+ if error_description:
357
+ params.append(f'error_description="{error_description}"')
358
+
359
+ if params:
360
+ parts.append(", ".join(params))
361
+
362
+ return " ".join(parts)
363
+
364
+ def validate_token(self, token: str) -> bool:
365
+ """
366
+ Validate an access token.
367
+
368
+ Args:
369
+ token: Access token to validate
370
+
371
+ Returns:
372
+ True if valid
373
+ """
374
+ # Check if we have this token stored
375
+ for stored_token in self._tokens.values():
376
+ if stored_token.access_token == token:
377
+ return not stored_token.is_expired()
378
+
379
+ # For external tokens, we'd need to call the introspection endpoint
380
+ # This is a simplified implementation
381
+ return True
382
+
383
+ def store_token(self, session_id: str, token: TokenResponse) -> None:
384
+ """Store a token for a session."""
385
+ self._tokens[session_id] = token
386
+ if self._token_store:
387
+ self._token_store(session_id, token)
388
+
389
+ def get_token(self, session_id: str) -> Optional[TokenResponse]:
390
+ """Get stored token for a session."""
391
+ return self._tokens.get(session_id)
392
+
393
+ def clear_token(self, session_id: str) -> None:
394
+ """Clear stored token for a session."""
395
+ if session_id in self._tokens:
396
+ del self._tokens[session_id]
397
+
398
+
399
+ async def discover_oauth_metadata(issuer_url: str) -> Dict[str, Any]:
400
+ """
401
+ Discover OAuth authorization server metadata.
402
+
403
+ Per RFC 8414, fetches from /.well-known/oauth-authorization-server
404
+
405
+ Args:
406
+ issuer_url: Authorization server issuer URL
407
+
408
+ Returns:
409
+ Metadata dictionary
410
+ """
411
+ try:
412
+ import httpx
413
+ except ImportError:
414
+ raise ImportError("httpx required for OAuth discovery. Install with: pip install httpx")
415
+
416
+ # Try OAuth 2.0 metadata endpoint first
417
+ metadata_url = f"{issuer_url.rstrip('/')}/.well-known/oauth-authorization-server"
418
+
419
+ async with httpx.AsyncClient() as client:
420
+ try:
421
+ response = await client.get(metadata_url)
422
+ if response.status_code == 200:
423
+ return response.json()
424
+ except Exception:
425
+ pass
426
+
427
+ # Fall back to OpenID Connect discovery
428
+ oidc_url = f"{issuer_url.rstrip('/')}/.well-known/openid-configuration"
429
+ response = await client.get(oidc_url)
430
+ response.raise_for_status()
431
+ return response.json()
432
+
433
+
434
+ def create_oauth_config_from_metadata(
435
+ metadata: Dict[str, Any],
436
+ client_id: str,
437
+ client_secret: Optional[str] = None,
438
+ scopes: Optional[List[str]] = None,
439
+ ) -> OAuthConfig:
440
+ """
441
+ Create OAuth config from discovered metadata.
442
+
443
+ Args:
444
+ metadata: Authorization server metadata
445
+ client_id: Client ID
446
+ client_secret: Optional client secret
447
+ scopes: Optional scopes
448
+
449
+ Returns:
450
+ OAuthConfig instance
451
+ """
452
+ return OAuthConfig(
453
+ authorization_endpoint=metadata["authorization_endpoint"],
454
+ token_endpoint=metadata["token_endpoint"],
455
+ client_id=client_id,
456
+ client_secret=client_secret,
457
+ scopes=scopes or metadata.get("scopes_supported", []),
458
+ issuer=metadata.get("issuer"),
459
+ metadata_url=metadata.get("issuer"),
460
+ )