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,289 @@
1
+ """
2
+ OpenID Connect Discovery Implementation
3
+
4
+ Implements OpenID Connect Discovery 1.0 per MCP 2025-11-25 specification.
5
+
6
+ Based on:
7
+ - OpenID Connect Discovery 1.0
8
+ - OAuth 2.0 Authorization Server Metadata (RFC8414)
9
+ """
10
+
11
+ import logging
12
+ import time
13
+ from dataclasses import dataclass, field
14
+ from typing import Any, Dict, List, Optional
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ @dataclass
20
+ class OIDCConfig:
21
+ """OpenID Connect configuration."""
22
+
23
+ # Required endpoints
24
+ issuer: str
25
+ authorization_endpoint: str
26
+ token_endpoint: str
27
+
28
+ # Optional endpoints
29
+ userinfo_endpoint: Optional[str] = None
30
+ jwks_uri: Optional[str] = None
31
+ registration_endpoint: Optional[str] = None
32
+ revocation_endpoint: Optional[str] = None
33
+ introspection_endpoint: Optional[str] = None
34
+ end_session_endpoint: Optional[str] = None
35
+
36
+ # Supported features
37
+ scopes_supported: List[str] = field(default_factory=list)
38
+ response_types_supported: List[str] = field(default_factory=list)
39
+ grant_types_supported: List[str] = field(default_factory=list)
40
+ subject_types_supported: List[str] = field(default_factory=list)
41
+ id_token_signing_alg_values_supported: List[str] = field(default_factory=list)
42
+ token_endpoint_auth_methods_supported: List[str] = field(default_factory=list)
43
+ claims_supported: List[str] = field(default_factory=list)
44
+ code_challenge_methods_supported: List[str] = field(default_factory=list)
45
+
46
+ # Cache metadata
47
+ _fetched_at: Optional[float] = None
48
+
49
+ def to_dict(self) -> Dict[str, Any]:
50
+ return {
51
+ "issuer": self.issuer,
52
+ "authorization_endpoint": self.authorization_endpoint,
53
+ "token_endpoint": self.token_endpoint,
54
+ "userinfo_endpoint": self.userinfo_endpoint,
55
+ "jwks_uri": self.jwks_uri,
56
+ "scopes_supported": self.scopes_supported,
57
+ "response_types_supported": self.response_types_supported,
58
+ "grant_types_supported": self.grant_types_supported,
59
+ "token_endpoint_auth_methods_supported": self.token_endpoint_auth_methods_supported,
60
+ "code_challenge_methods_supported": self.code_challenge_methods_supported,
61
+ }
62
+
63
+ @classmethod
64
+ def from_dict(cls, data: Dict[str, Any]) -> "OIDCConfig":
65
+ return cls(
66
+ issuer=data["issuer"],
67
+ authorization_endpoint=data["authorization_endpoint"],
68
+ token_endpoint=data["token_endpoint"],
69
+ userinfo_endpoint=data.get("userinfo_endpoint"),
70
+ jwks_uri=data.get("jwks_uri"),
71
+ registration_endpoint=data.get("registration_endpoint"),
72
+ revocation_endpoint=data.get("revocation_endpoint"),
73
+ introspection_endpoint=data.get("introspection_endpoint"),
74
+ end_session_endpoint=data.get("end_session_endpoint"),
75
+ scopes_supported=data.get("scopes_supported", []),
76
+ response_types_supported=data.get("response_types_supported", []),
77
+ grant_types_supported=data.get("grant_types_supported", []),
78
+ subject_types_supported=data.get("subject_types_supported", []),
79
+ id_token_signing_alg_values_supported=data.get("id_token_signing_alg_values_supported", []),
80
+ token_endpoint_auth_methods_supported=data.get("token_endpoint_auth_methods_supported", []),
81
+ claims_supported=data.get("claims_supported", []),
82
+ code_challenge_methods_supported=data.get("code_challenge_methods_supported", []),
83
+ _fetched_at=time.time(),
84
+ )
85
+
86
+
87
+ class OIDCDiscovery:
88
+ """
89
+ OpenID Connect Discovery client.
90
+
91
+ Fetches and caches OIDC configuration from well-known endpoints.
92
+ """
93
+
94
+ def __init__(
95
+ self,
96
+ cache_ttl: int = 3600,
97
+ ):
98
+ """
99
+ Initialize OIDC discovery client.
100
+
101
+ Args:
102
+ cache_ttl: Cache TTL in seconds
103
+ """
104
+ self._cache: Dict[str, OIDCConfig] = {}
105
+ self._cache_ttl = cache_ttl
106
+
107
+ async def discover(self, issuer: str, force_refresh: bool = False) -> OIDCConfig:
108
+ """
109
+ Discover OIDC configuration for an issuer.
110
+
111
+ Args:
112
+ issuer: Issuer URL
113
+ force_refresh: Force cache refresh
114
+
115
+ Returns:
116
+ OIDCConfig instance
117
+ """
118
+ # Check cache
119
+ if not force_refresh and issuer in self._cache:
120
+ config = self._cache[issuer]
121
+ if config._fetched_at and time.time() - config._fetched_at < self._cache_ttl:
122
+ return config
123
+
124
+ # Fetch configuration
125
+ config = await self._fetch_config(issuer)
126
+ self._cache[issuer] = config
127
+
128
+ return config
129
+
130
+ async def _fetch_config(self, issuer: str) -> OIDCConfig:
131
+ """Fetch OIDC configuration from well-known endpoint."""
132
+ try:
133
+ import httpx
134
+ except ImportError:
135
+ raise ImportError("httpx required for OIDC discovery. Install with: pip install httpx")
136
+
137
+ # Build well-known URL
138
+ discovery_url = f"{issuer.rstrip('/')}/.well-known/openid-configuration"
139
+
140
+ async with httpx.AsyncClient() as client:
141
+ response = await client.get(discovery_url, follow_redirects=True)
142
+ response.raise_for_status()
143
+
144
+ data = response.json()
145
+
146
+ # Validate issuer matches
147
+ if data.get("issuer") != issuer and data.get("issuer") != issuer.rstrip("/"):
148
+ logger.warning(f"Issuer mismatch: expected {issuer}, got {data.get('issuer')}")
149
+
150
+ return OIDCConfig.from_dict(data)
151
+
152
+ def get_cached(self, issuer: str) -> Optional[OIDCConfig]:
153
+ """Get cached configuration without fetching."""
154
+ return self._cache.get(issuer)
155
+
156
+ def clear_cache(self, issuer: Optional[str] = None) -> None:
157
+ """Clear cached configurations."""
158
+ if issuer:
159
+ if issuer in self._cache:
160
+ del self._cache[issuer]
161
+ else:
162
+ self._cache.clear()
163
+
164
+
165
+ @dataclass
166
+ class ClientMetadata:
167
+ """
168
+ OAuth Client ID Metadata Document.
169
+
170
+ Per draft-ietf-oauth-client-id-metadata-document-00
171
+ """
172
+ client_id: str
173
+ client_name: Optional[str] = None
174
+ client_uri: Optional[str] = None
175
+ logo_uri: Optional[str] = None
176
+ contacts: List[str] = field(default_factory=list)
177
+ tos_uri: Optional[str] = None
178
+ policy_uri: Optional[str] = None
179
+
180
+ # Redirect URIs
181
+ redirect_uris: List[str] = field(default_factory=list)
182
+
183
+ # Grant types
184
+ grant_types: List[str] = field(default_factory=lambda: ["authorization_code"])
185
+ response_types: List[str] = field(default_factory=lambda: ["code"])
186
+
187
+ # Token endpoint auth
188
+ token_endpoint_auth_method: str = "client_secret_basic"
189
+
190
+ # Scopes
191
+ scope: Optional[str] = None
192
+
193
+ # Software statement
194
+ software_id: Optional[str] = None
195
+ software_version: Optional[str] = None
196
+
197
+ def to_dict(self) -> Dict[str, Any]:
198
+ result = {"client_id": self.client_id}
199
+
200
+ if self.client_name:
201
+ result["client_name"] = self.client_name
202
+ if self.client_uri:
203
+ result["client_uri"] = self.client_uri
204
+ if self.logo_uri:
205
+ result["logo_uri"] = self.logo_uri
206
+ if self.contacts:
207
+ result["contacts"] = self.contacts
208
+ if self.redirect_uris:
209
+ result["redirect_uris"] = self.redirect_uris
210
+ if self.grant_types:
211
+ result["grant_types"] = self.grant_types
212
+ if self.response_types:
213
+ result["response_types"] = self.response_types
214
+ if self.token_endpoint_auth_method:
215
+ result["token_endpoint_auth_method"] = self.token_endpoint_auth_method
216
+ if self.scope:
217
+ result["scope"] = self.scope
218
+
219
+ return result
220
+
221
+ @classmethod
222
+ def from_dict(cls, data: Dict[str, Any]) -> "ClientMetadata":
223
+ return cls(
224
+ client_id=data["client_id"],
225
+ client_name=data.get("client_name"),
226
+ client_uri=data.get("client_uri"),
227
+ logo_uri=data.get("logo_uri"),
228
+ contacts=data.get("contacts", []),
229
+ tos_uri=data.get("tos_uri"),
230
+ policy_uri=data.get("policy_uri"),
231
+ redirect_uris=data.get("redirect_uris", []),
232
+ grant_types=data.get("grant_types", ["authorization_code"]),
233
+ response_types=data.get("response_types", ["code"]),
234
+ token_endpoint_auth_method=data.get("token_endpoint_auth_method", "client_secret_basic"),
235
+ scope=data.get("scope"),
236
+ software_id=data.get("software_id"),
237
+ software_version=data.get("software_version"),
238
+ )
239
+
240
+
241
+ async def fetch_client_metadata(client_id_url: str) -> ClientMetadata:
242
+ """
243
+ Fetch OAuth Client ID Metadata Document.
244
+
245
+ Per draft-ietf-oauth-client-id-metadata-document-00
246
+
247
+ Args:
248
+ client_id_url: URL of the client ID metadata document
249
+
250
+ Returns:
251
+ ClientMetadata instance
252
+ """
253
+ try:
254
+ import httpx
255
+ except ImportError:
256
+ raise ImportError("httpx required for client metadata. Install with: pip install httpx")
257
+
258
+ async with httpx.AsyncClient() as client:
259
+ response = await client.get(client_id_url, follow_redirects=True)
260
+ response.raise_for_status()
261
+
262
+ data = response.json()
263
+ return ClientMetadata.from_dict(data)
264
+
265
+
266
+ async def fetch_protected_resource_metadata(resource_url: str) -> Dict[str, Any]:
267
+ """
268
+ Fetch OAuth 2.0 Protected Resource Metadata.
269
+
270
+ Per RFC 9728
271
+
272
+ Args:
273
+ resource_url: URL of the protected resource
274
+
275
+ Returns:
276
+ Metadata dictionary
277
+ """
278
+ try:
279
+ import httpx
280
+ except ImportError:
281
+ raise ImportError("httpx required for resource metadata. Install with: pip install httpx")
282
+
283
+ # Build well-known URL
284
+ metadata_url = f"{resource_url.rstrip('/')}/.well-known/oauth-protected-resource"
285
+
286
+ async with httpx.AsyncClient() as client:
287
+ response = await client.get(metadata_url, follow_redirects=True)
288
+ response.raise_for_status()
289
+ return response.json()
@@ -0,0 +1,260 @@
1
+ """
2
+ MCP Scope Management
3
+
4
+ Implements incremental scope handling per MCP 2025-11-25 specification.
5
+
6
+ Features:
7
+ - Scope validation and enforcement
8
+ - WWW-Authenticate challenges for scope escalation
9
+ - Scope hierarchy support
10
+ """
11
+
12
+ import logging
13
+ from dataclasses import dataclass
14
+ from typing import Any, Dict, List, Optional, Set
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ # Standard MCP scopes
20
+ MCP_SCOPES = {
21
+ "tools:read": "Read tool definitions",
22
+ "tools:call": "Execute tools",
23
+ "resources:read": "Read resources",
24
+ "resources:subscribe": "Subscribe to resource changes",
25
+ "prompts:read": "Read prompts",
26
+ "prompts:execute": "Execute prompts",
27
+ "sampling:create": "Create sampling requests",
28
+ "tasks:read": "Read tasks",
29
+ "tasks:write": "Create and manage tasks",
30
+ "admin": "Administrative access",
31
+ }
32
+
33
+
34
+ @dataclass
35
+ class ScopeChallenge:
36
+ """
37
+ Scope challenge for incremental consent.
38
+
39
+ Used when an operation requires additional scopes.
40
+ """
41
+ required_scopes: List[str]
42
+ granted_scopes: List[str]
43
+ missing_scopes: List[str]
44
+ error: str = "insufficient_scope"
45
+ error_description: Optional[str] = None
46
+
47
+ def to_www_authenticate(self, realm: Optional[str] = None) -> str:
48
+ """Generate WWW-Authenticate header value."""
49
+ parts = ["Bearer"]
50
+ params = []
51
+
52
+ if realm:
53
+ params.append(f'realm="{realm}"')
54
+
55
+ if self.missing_scopes:
56
+ params.append(f'scope="{" ".join(self.missing_scopes)}"')
57
+
58
+ params.append(f'error="{self.error}"')
59
+
60
+ if self.error_description:
61
+ params.append(f'error_description="{self.error_description}"')
62
+
63
+ if params:
64
+ parts.append(", ".join(params))
65
+
66
+ return " ".join(parts)
67
+
68
+ def to_dict(self) -> Dict[str, Any]:
69
+ return {
70
+ "required_scopes": self.required_scopes,
71
+ "granted_scopes": self.granted_scopes,
72
+ "missing_scopes": self.missing_scopes,
73
+ "error": self.error,
74
+ "error_description": self.error_description,
75
+ }
76
+
77
+
78
+ class ScopeManager:
79
+ """
80
+ MCP Scope Manager.
81
+
82
+ Handles scope validation, enforcement, and incremental consent.
83
+ """
84
+
85
+ def __init__(
86
+ self,
87
+ available_scopes: Optional[Dict[str, str]] = None,
88
+ scope_hierarchy: Optional[Dict[str, List[str]]] = None,
89
+ ):
90
+ """
91
+ Initialize scope manager.
92
+
93
+ Args:
94
+ available_scopes: Available scopes with descriptions
95
+ scope_hierarchy: Scope hierarchy (parent -> children)
96
+ """
97
+ self._available_scopes = available_scopes or MCP_SCOPES.copy()
98
+ self._scope_hierarchy = scope_hierarchy or {
99
+ "admin": list(MCP_SCOPES.keys()),
100
+ "tools:call": ["tools:read"],
101
+ "resources:subscribe": ["resources:read"],
102
+ "tasks:write": ["tasks:read"],
103
+ }
104
+
105
+ def expand_scopes(self, scopes: List[str]) -> Set[str]:
106
+ """
107
+ Expand scopes based on hierarchy.
108
+
109
+ Args:
110
+ scopes: List of scopes
111
+
112
+ Returns:
113
+ Expanded set of scopes
114
+ """
115
+ expanded = set(scopes)
116
+
117
+ for scope in scopes:
118
+ if scope in self._scope_hierarchy:
119
+ expanded.update(self._scope_hierarchy[scope])
120
+
121
+ return expanded
122
+
123
+ def validate_scopes(
124
+ self,
125
+ required: List[str],
126
+ granted: List[str],
127
+ ) -> tuple[bool, Optional[ScopeChallenge]]:
128
+ """
129
+ Validate that granted scopes satisfy requirements.
130
+
131
+ Args:
132
+ required: Required scopes
133
+ granted: Granted scopes
134
+
135
+ Returns:
136
+ Tuple of (is_valid, challenge)
137
+ """
138
+ # Expand granted scopes
139
+ expanded_granted = self.expand_scopes(granted)
140
+
141
+ # Check each required scope
142
+ missing = []
143
+ for scope in required:
144
+ if scope not in expanded_granted:
145
+ missing.append(scope)
146
+
147
+ if missing:
148
+ return False, ScopeChallenge(
149
+ required_scopes=required,
150
+ granted_scopes=granted,
151
+ missing_scopes=missing,
152
+ error_description=f"Missing required scopes: {', '.join(missing)}",
153
+ )
154
+
155
+ return True, None
156
+
157
+ def check_scope(
158
+ self,
159
+ scope: str,
160
+ granted: List[str],
161
+ ) -> bool:
162
+ """
163
+ Check if a single scope is granted.
164
+
165
+ Args:
166
+ scope: Required scope
167
+ granted: Granted scopes
168
+
169
+ Returns:
170
+ True if scope is granted
171
+ """
172
+ expanded = self.expand_scopes(granted)
173
+ return scope in expanded
174
+
175
+ def get_scope_description(self, scope: str) -> Optional[str]:
176
+ """Get description for a scope."""
177
+ return self._available_scopes.get(scope)
178
+
179
+ def list_available_scopes(self) -> Dict[str, str]:
180
+ """List all available scopes with descriptions."""
181
+ return self._available_scopes.copy()
182
+
183
+ def add_scope(self, scope: str, description: str) -> None:
184
+ """Add a custom scope."""
185
+ self._available_scopes[scope] = description
186
+
187
+ def add_hierarchy(self, parent: str, children: List[str]) -> None:
188
+ """Add scope hierarchy."""
189
+ if parent in self._scope_hierarchy:
190
+ self._scope_hierarchy[parent].extend(children)
191
+ else:
192
+ self._scope_hierarchy[parent] = children
193
+
194
+
195
+ @dataclass
196
+ class ScopeRequirement:
197
+ """Scope requirement for an operation."""
198
+ scopes: List[str]
199
+ any_of: bool = False # If True, any scope is sufficient
200
+ description: Optional[str] = None
201
+
202
+ def check(self, granted: List[str], manager: ScopeManager) -> tuple[bool, Optional[ScopeChallenge]]:
203
+ """Check if requirement is satisfied."""
204
+ if self.any_of:
205
+ # Any scope is sufficient
206
+ for scope in self.scopes:
207
+ if manager.check_scope(scope, granted):
208
+ return True, None
209
+
210
+ return False, ScopeChallenge(
211
+ required_scopes=self.scopes,
212
+ granted_scopes=granted,
213
+ missing_scopes=self.scopes,
214
+ error_description=f"Requires one of: {', '.join(self.scopes)}",
215
+ )
216
+ else:
217
+ # All scopes required
218
+ return manager.validate_scopes(self.scopes, granted)
219
+
220
+
221
+ # Operation to scope mapping
222
+ OPERATION_SCOPES: Dict[str, ScopeRequirement] = {
223
+ "tools/list": ScopeRequirement(["tools:read"]),
224
+ "tools/call": ScopeRequirement(["tools:call"]),
225
+ "resources/list": ScopeRequirement(["resources:read"]),
226
+ "resources/read": ScopeRequirement(["resources:read"]),
227
+ "resources/subscribe": ScopeRequirement(["resources:subscribe"]),
228
+ "prompts/list": ScopeRequirement(["prompts:read"]),
229
+ "prompts/get": ScopeRequirement(["prompts:read"]),
230
+ "sampling/createMessage": ScopeRequirement(["sampling:create"]),
231
+ "tasks/create": ScopeRequirement(["tasks:write"]),
232
+ "tasks/get": ScopeRequirement(["tasks:read"]),
233
+ "tasks/list": ScopeRequirement(["tasks:read"]),
234
+ "tasks/cancel": ScopeRequirement(["tasks:write"]),
235
+ }
236
+
237
+
238
+ def get_operation_scopes(operation: str) -> Optional[ScopeRequirement]:
239
+ """Get scope requirement for an operation."""
240
+ return OPERATION_SCOPES.get(operation)
241
+
242
+
243
+ def require_scope(*scopes: str, any_of: bool = False):
244
+ """
245
+ Decorator to require scopes for a function.
246
+
247
+ Args:
248
+ scopes: Required scopes
249
+ any_of: If True, any scope is sufficient
250
+
251
+ Returns:
252
+ Decorator function
253
+ """
254
+ requirement = ScopeRequirement(list(scopes), any_of=any_of)
255
+
256
+ def decorator(func):
257
+ func._scope_requirement = requirement
258
+ return func
259
+
260
+ return decorator