langflow-base-nightly 0.5.1.dev2__py3-none-any.whl → 0.5.1.dev4__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 (935) hide show
  1. langflow/__init__.py +215 -0
  2. langflow/__main__.py +16 -2
  3. langflow/alembic/versions/006b3990db50_add_unique_constraints.py +4 -7
  4. langflow/alembic/versions/012fb73ac359_add_folder_table.py +4 -5
  5. langflow/alembic/versions/0ae3a2674f32_update_the_columns_that_need_to_change_.py +11 -20
  6. langflow/alembic/versions/0b8757876a7c_.py +4 -7
  7. langflow/alembic/versions/0d60fcbd4e8e_create_vertex_builds_table.py +4 -6
  8. langflow/alembic/versions/1a110b568907_replace_credential_table_with_variable.py +4 -5
  9. langflow/alembic/versions/1b8b740a6fa3_remove_fk_constraint_in_message_.py +32 -27
  10. langflow/alembic/versions/1c79524817ed_add_unique_constraints_per_user_in_.py +4 -5
  11. langflow/alembic/versions/1d90f8a0efe1_update_description_columns_type.py +4 -5
  12. langflow/alembic/versions/1eab2c3eb45e_event_error.py +14 -15
  13. langflow/alembic/versions/1ef9c4f3765d_.py +5 -10
  14. langflow/alembic/versions/1f4d6df60295_add_default_fields_column.py +4 -5
  15. langflow/alembic/versions/260dbcc8b680_adds_tables.py +4 -5
  16. langflow/alembic/versions/29fe8f1f806b_add_missing_index.py +4 -5
  17. langflow/alembic/versions/2ac71eb9c3ae_adds_credential_table.py +4 -7
  18. langflow/alembic/versions/3bb0ddf32dfb_add_unique_constraints_per_user_in_flow_.py +4 -5
  19. langflow/alembic/versions/4e5980a44eaa_fix_date_times_again.py +1 -2
  20. langflow/alembic/versions/58b28437a398_modify_nullable.py +1 -2
  21. langflow/alembic/versions/5ace73a7f223_new_remove_table_upgrade_op.py +6 -12
  22. langflow/alembic/versions/631faacf5da2_add_webhook_columns.py +4 -5
  23. langflow/alembic/versions/63b9c451fd30_add_icon_and_icon_bg_color_to_flow.py +4 -5
  24. langflow/alembic/versions/66f72f04a1de_add_mcp_support_with_project_settings_.py +21 -23
  25. langflow/alembic/versions/67cc006d50bf_add_profile_image_column.py +4 -5
  26. langflow/alembic/versions/6e7b581b5648_fix_nullable.py +4 -5
  27. langflow/alembic/versions/7843803a87b5_store_updates.py +4 -6
  28. langflow/alembic/versions/79e675cb6752_change_datetime_type.py +1 -2
  29. langflow/alembic/versions/7d2162acc8b2_adds_updated_at_and_folder_cols.py +4 -10
  30. langflow/alembic/versions/90be8e2ed91e_create_transactions_table.py +4 -6
  31. langflow/alembic/versions/93e2705fa8d6_add_column_save_path_to_flow.py +7 -9
  32. langflow/alembic/versions/a72f5cf9c2f9_add_endpoint_name_col.py +4 -5
  33. langflow/alembic/versions/b2fa308044b5_add_unique_constraints.py +1 -2
  34. langflow/alembic/versions/bc2f01c40e4a_new_fixes.py +4 -5
  35. langflow/alembic/versions/c153816fd85f_set_name_and_value_to_not_nullable.py +4 -5
  36. langflow/alembic/versions/d066bfd22890_add_message_table.py +4 -4
  37. langflow/alembic/versions/d2d475a1f7c0_add_tags_column_to_flow.py +12 -13
  38. langflow/alembic/versions/d3dbf656a499_add_gradient_column_in_flow.py +12 -12
  39. langflow/alembic/versions/d9a6ea21edcd_rename_default_folder.py +7 -10
  40. langflow/alembic/versions/dd9e0804ebd1_add_v2_file_table.py +8 -7
  41. langflow/alembic/versions/e3162c1804e6_add_persistent_locked_state.py +10 -10
  42. langflow/alembic/versions/e3bc869fa272_fix_nullable.py +4 -5
  43. langflow/alembic/versions/e56d87f8994a_add_optins_column_to_user.py +13 -14
  44. langflow/alembic/versions/e5a65ecff2cd_nullable_in_vertex_build.py +4 -5
  45. langflow/alembic/versions/eb5866d51fd2_change_columns_to_be_nullable.py +4 -5
  46. langflow/alembic/versions/eb5e72293a8e_add_error_and_edit_flags_to_message.py +4 -5
  47. langflow/alembic/versions/f3b2d1f1002d_add_column_access_type_to_flow.py +19 -15
  48. langflow/alembic/versions/f5ee9749d1a6_user_id_can_be_null_in_flow.py +4 -6
  49. langflow/alembic/versions/fd531f8868b1_fix_credential_table.py +5 -8
  50. langflow/api/build.py +5 -4
  51. langflow/api/health_check_router.py +1 -1
  52. langflow/api/limited_background_tasks.py +1 -1
  53. langflow/api/log_router.py +1 -2
  54. langflow/api/utils.py +2 -2
  55. langflow/api/v1/base.py +1 -2
  56. langflow/api/v1/callback.py +4 -9
  57. langflow/api/v1/chat.py +6 -7
  58. langflow/api/v1/endpoints.py +15 -15
  59. langflow/api/v1/files.py +1 -1
  60. langflow/api/v1/flows.py +1 -1
  61. langflow/api/v1/knowledge_bases.py +1 -1
  62. langflow/api/v1/mcp.py +1 -1
  63. langflow/api/v1/mcp_projects.py +14 -5
  64. langflow/api/v1/mcp_utils.py +3 -3
  65. langflow/api/v1/openai_responses.py +4 -4
  66. langflow/api/v1/schemas.py +3 -38
  67. langflow/api/v1/starter_projects.py +61 -3
  68. langflow/api/v1/store.py +1 -1
  69. langflow/api/v1/validate.py +3 -3
  70. langflow/api/v1/voice_mode.py +2 -2
  71. langflow/api/v2/files.py +1 -1
  72. langflow/api/v2/mcp.py +2 -2
  73. langflow/base/__init__.py +11 -0
  74. langflow/base/agents/__init__.py +3 -0
  75. langflow/base/data/__init__.py +2 -4
  76. langflow/base/data/utils.py +2 -197
  77. langflow/base/embeddings/__init__.py +3 -0
  78. langflow/base/io/__init__.py +7 -0
  79. langflow/base/io/chat.py +5 -18
  80. langflow/base/io/text.py +2 -21
  81. langflow/base/knowledge_bases/__init__.py +3 -0
  82. langflow/base/memory/__init__.py +3 -0
  83. langflow/base/models/__init__.py +2 -2
  84. langflow/base/models/openai_constants.py +6 -120
  85. langflow/base/prompts/__init__.py +3 -0
  86. langflow/base/prompts/api_utils.py +2 -223
  87. langflow/base/textsplitters/__init__.py +3 -0
  88. langflow/base/tools/__init__.py +3 -0
  89. langflow/base/vectorstores/__init__.py +3 -0
  90. langflow/components/__init__.py +7 -259
  91. langflow/components/agents.py +6 -0
  92. langflow/components/anthropic.py +6 -0
  93. langflow/components/data.py +6 -0
  94. langflow/components/helpers.py +6 -0
  95. langflow/components/knowledge_bases/ingestion.py +13 -14
  96. langflow/components/knowledge_bases/retrieval.py +8 -7
  97. langflow/components/openai.py +6 -0
  98. langflow/components/processing/__init__.py +1 -117
  99. langflow/components/processing/converter.py +3 -149
  100. langflow/custom/__init__.py +26 -3
  101. langflow/custom/custom_component/__init__.py +4 -0
  102. langflow/custom/custom_component/component.py +20 -1738
  103. langflow/custom/custom_component/component_with_cache.py +1 -8
  104. langflow/custom/custom_component/custom_component.py +1 -552
  105. langflow/custom/utils.py +1 -872
  106. langflow/custom/validate.py +1 -0
  107. langflow/events/event_manager.py +18 -108
  108. langflow/field_typing/__init__.py +6 -6
  109. langflow/field_typing/constants.py +87 -122
  110. langflow/field_typing/range_spec.py +2 -32
  111. langflow/frontend/assets/{SlackIcon-Cc7Qnzki.js → SlackIcon-v88osOTA.js} +1 -1
  112. langflow/frontend/assets/{Wikipedia-7ulMZY46.js → Wikipedia-DD_S2k00.js} +1 -1
  113. langflow/frontend/assets/{Wolfram-By9PGsHS.js → Wolfram-EO2C5noN.js} +1 -1
  114. langflow/frontend/assets/{index-DVLIDc2_.js → index-1Gv1mfvk.js} +1 -1
  115. langflow/frontend/assets/{index-MVW4HTEk.js → index-7v-bzlzf.js} +1 -1
  116. langflow/frontend/assets/{index-CUzlcce2.js → index-9CbMazbV.js} +1 -1
  117. langflow/frontend/assets/{index-CU16NJD7.js → index-B8ZHP8g2.js} +1 -1
  118. langflow/frontend/assets/{index-v8eXbWlM.js → index-B8y2e6vN.js} +1 -1
  119. langflow/frontend/assets/{index-BX_asvRB.js → index-BBRUGsyr.js} +1 -1
  120. langflow/frontend/assets/{index-9FL5xjkL.js → index-BGwqQwlh.js} +1 -1
  121. langflow/frontend/assets/{index-BAn-AzCS.js → index-BIq-k-FG.js} +1 -1
  122. langflow/frontend/assets/{index-D5c2nNvp.js → index-BSN73YP8.js} +1 -1
  123. langflow/frontend/assets/{index-DMCerPJM.js → index-BU8R8jRn.js} +1 -1
  124. langflow/frontend/assets/{index-CvSoff-8.js → index-BV6yx8ey.js} +1 -1
  125. langflow/frontend/assets/{index-BISPW-f6.js → index-BYIsg-Eh.js} +1 -1
  126. langflow/frontend/assets/{index-GzOGB_fo.js → index-B_ksDBSQ.js} +1 -1
  127. langflow/frontend/assets/{index-BIqEYjNT.js → index-Ba1UOZ9A.js} +1 -1
  128. langflow/frontend/assets/{index-ByxGmq5p.js → index-Ba9tKRQg.js} +1 -1
  129. langflow/frontend/assets/{index-BLEWsL1U.js → index-Bbfaw8ca.js} +1 -1
  130. langflow/frontend/assets/{index-C_MhBX6R.js → index-BbuGqvAx.js} +1 -1
  131. langflow/frontend/assets/{index-RH_I78z_.js → index-BeoXu1YX.js} +1 -1
  132. langflow/frontend/assets/{index-cYFKmtmg.js → index-BfjZmOnH.js} +1 -1
  133. langflow/frontend/assets/{index-Bm9i8F4W.js → index-Bjzy_HZB.js} +1 -1
  134. langflow/frontend/assets/{index-_szO7sta.js → index-BofEkpYB.js} +1 -1
  135. langflow/frontend/assets/{index-DP1oE6QB.js → index-Bp7Mty2H.js} +1 -1
  136. langflow/frontend/assets/{index-CeswGUz3.js → index-BqX1H6yK.js} +1 -1
  137. langflow/frontend/assets/{index-C8pI0lzi.js → index-BqtBAJAN.js} +1 -1
  138. langflow/frontend/assets/{index-BusCv3bR.js → index-Bsfraj7A.js} +1 -1
  139. langflow/frontend/assets/{index-BWnKMRFJ.js → index-BtFl7fER.js} +1 -1
  140. langflow/frontend/assets/{index-DnlVWWU8.js → index-BvX993Sv.js} +1 -1
  141. langflow/frontend/assets/{index-C676MS3I.js → index-BvgQ2vzM.js} +1 -1
  142. langflow/frontend/assets/{index-DJ6HD14g.js → index-BwY98u8n.js} +1 -1
  143. langflow/frontend/assets/{index-C51yNvIL.js → index-C-RIJAOS.js} +1 -1
  144. langflow/frontend/assets/{index-DiblXWmk.js → index-C1K6A38P.js} +1 -1
  145. langflow/frontend/assets/{index-Co__gFM1.js → index-C3Vwhx0t.js} +1 -1
  146. langflow/frontend/assets/{index-Coi86oqP.js → index-C5XUG_gr.js} +1 -1
  147. langflow/frontend/assets/{index-jwzN3Jd_.js → index-C6ouLG9o.js} +1 -1
  148. langflow/frontend/assets/{index-CQQ-4XMS.js → index-C7ZJ_Z6f.js} +1 -1
  149. langflow/frontend/assets/{index-Bl7RpmrB.js → index-CCOGIwGY.js} +1 -1
  150. langflow/frontend/assets/{index-CVkIdc6y.js → index-CCcye2rt.js} +1 -1
  151. langflow/frontend/assets/{index-bMhyLtgS.js → index-CFR4yJQB.js} +1 -1
  152. langflow/frontend/assets/{index-aAgSKWb3.js → index-CIGmPP0H.js} +1 -1
  153. langflow/frontend/assets/{index-BGt6jQ4x.js → index-CJmMEa6d.js} +1 -1
  154. langflow/frontend/assets/{index-DX7JcSMz.js → index-CJxD7lyU.js} +1 -1
  155. langflow/frontend/assets/{index-BZ-A4K98.js → index-CL_vu6ut.js} +1 -1
  156. langflow/frontend/assets/{index-BMpKFGhI.js → index-COf3UnBn.js} +1 -1
  157. langflow/frontend/assets/{index-xN8ogFdo.js → index-CV9650h_.js} +1 -1
  158. langflow/frontend/assets/{index-OsUvqIUr.js → index-CVDzych0.js} +1 -1
  159. langflow/frontend/assets/{index-BH7AyHxp.js → index-CWIHsC4D.js} +1 -1
  160. langflow/frontend/assets/{index-mjwtJmkP.js → index-CXCnFZ0L.js} +1 -1
  161. langflow/frontend/assets/{index-3jlSQi5Y.js → index-Ca_Pw_Dn.js} +1 -1
  162. langflow/frontend/assets/{index-D-SnFlhU.js → index-Cbb3bX9e.js} +1 -1
  163. langflow/frontend/assets/{index--e0oQqZh.js → index-CcJtOz-Z.js} +1 -1
  164. langflow/frontend/assets/{index-S-sc0Cm9.js → index-CfTbTHEv.js} +1 -1
  165. langflow/frontend/assets/{index-Deu8rlaZ.js → index-ChoxDAgX.js} +1 -1
  166. langflow/frontend/assets/{index-lnF9Eqr2.js → index-Cn4gw8aE.js} +1 -1
  167. langflow/frontend/assets/{index-C_NwzK6j.js → index-CnpLg4zX.js} +1 -1
  168. langflow/frontend/assets/{index-DznH7Jbq.js → index-Cpao2omG.js} +1 -1
  169. langflow/frontend/assets/{index-DpWrk8mA.js → index-CqoxM01j.js} +1 -1
  170. langflow/frontend/assets/{index-Bw-TIIC6.js → index-CrHf2Ic1.js} +1 -1
  171. langflow/frontend/assets/{index-DmYLDQag.js → index-CrV0uIjp.js} +1 -1
  172. langflow/frontend/assets/{index-Dp7ZQyL3.js → index-CssADaak.js} +1 -1
  173. langflow/frontend/assets/{index-CNh0rwur.js → index-CtJdNLy9.js} +1 -1
  174. langflow/frontend/assets/{index-Ca1b7Iag.js → index-CyeWD2dh.js} +1 -1
  175. langflow/frontend/assets/{index-DcApTyZ7.js → index-D1xzD7uc.js} +1 -1
  176. langflow/frontend/assets/{index-B3GvPjhD.js → index-D6MuXC4L.js} +1 -1
  177. langflow/frontend/assets/{index-Cw0UComa.js → index-D8w9zvIF.js} +1 -1
  178. langflow/frontend/assets/{index-C-2MRYoJ.js → index-D98Gn0A6.js} +1 -1
  179. langflow/frontend/assets/{index-aWnZIwHd.js → index-DBhjpWkf.js} +1 -1
  180. langflow/frontend/assets/{index-nw3WF9lY.js → index-DCCRJzcY.js} +1 -1
  181. langflow/frontend/assets/{index-RjeC0kaX.js → index-DCTRSkEW.js} +1 -1
  182. langflow/frontend/assets/{index-B_kBTgxV.js → index-DCUfitVj.js} +1 -1
  183. langflow/frontend/assets/{index-ChsGhZn3.js → index-DDdz-Xcl.js} +1 -1
  184. langflow/frontend/assets/{index-7yAHPRxv.js → index-DGdMwZjG.js} +1 -1
  185. langflow/frontend/assets/{index-DjQElpEg.js → index-DGtl2vMw.js} +1 -1
  186. langflow/frontend/assets/{index-BCXhKCOK.js → index-DHVdkrni.js} +1 -1
  187. langflow/frontend/assets/{index-S8uJXTOq.js → index-DJBWwjgl.js} +1 -1
  188. langflow/frontend/assets/{index-qiVTWUuf.js → index-DMAkJ_qX.js} +1 -1
  189. langflow/frontend/assets/{index-D-WStJI6.js → index-DMEvEQI5.js} +1 -1
  190. langflow/frontend/assets/{index-BhqVw9WQ.js → index-DNGRoOsp.js} +1 -1
  191. langflow/frontend/assets/{index-Cu7vC48Y.js → index-DNT_TUTa.js} +1 -1
  192. langflow/frontend/assets/{index-Bhcv5M0n.js → index-DQKOH_9K.js} +1 -1
  193. langflow/frontend/assets/{index-CLcaktde.js → index-DQhqqtqQ.js} +1 -1
  194. langflow/frontend/assets/{index-DZVgPCio.js → index-DRM7KKnG.js} +1 -1
  195. langflow/frontend/assets/{index-uybez8MR.js → index-DSCtl3a5.js} +1 -1
  196. langflow/frontend/assets/{index-CJ5A6STv.js → index-DSLNlm0Z.js} +1 -1
  197. langflow/frontend/assets/{index-Drg8me2a.js → index-DT-PspE-.js} +1 -1
  198. langflow/frontend/assets/{index-DsEZjOcp.js → index-DTpbH-p8.js} +1 -1
  199. langflow/frontend/assets/{index-DrXXKzpD.js → index-DWV6MsIq.js} +1 -1
  200. langflow/frontend/assets/{index-4JIEdyIM.js → index-DWeL4US_.js} +1 -1
  201. langflow/frontend/assets/{index-BlDsBQ_1.js → index-DYKZHhpU.js} +1 -1
  202. langflow/frontend/assets/{index-DFY8YFbC.js → index-DZyQHiMR.js} +1 -1
  203. langflow/frontend/assets/{index-CKPZpkQk.js → index-Dc6qVuSa.js} +1 -1
  204. langflow/frontend/assets/{index-yyAaYjLR.js → index-DkYuicnC.js} +1 -1
  205. langflow/frontend/assets/{index-DmVt5Jlx.js → index-Dlj_2mMs.js} +1 -1
  206. langflow/frontend/assets/{index-BvRIG6P5.js → index-DmGJUrEp.js} +1 -1
  207. langflow/frontend/assets/{index-BWFIrwW1.js → index-Dn6hpCAZ.js} +1 -1
  208. langflow/frontend/assets/{index-Cb5G9Ifd.js → index-DrJU8Fgb.js} +1 -1
  209. langflow/frontend/assets/{index-COoTCxvs.js → index-DsWfdCzp.js} +1 -1
  210. langflow/frontend/assets/{index-ZjeocHyu.js → index-DvCPWs2_.js} +1 -1
  211. langflow/frontend/assets/{index-B5LHnuQR.js → index-DvPVq7OP.js} +1 -1
  212. langflow/frontend/assets/{index-BnCnYnao.js → index-Dw71ufW4.js} +1 -1
  213. langflow/frontend/assets/{index-AALDfCyt.js → index-DxkJactf.js} +1 -1
  214. langflow/frontend/assets/{index-k9jP5chN.js → index-Dz2GTphU.js} +1 -1
  215. langflow/frontend/assets/{index-BdjfHsrf.js → index-Fvd524_c.js} +1 -1
  216. langflow/frontend/assets/{index-AKVkmT4S.js → index-GAQ0Mk2M.js} +1 -1
  217. langflow/frontend/assets/{index-BZSa2qz7.js → index-Hm5-4ItD.js} +1 -1
  218. langflow/frontend/assets/{index-DbfS_UH-.js → index-IT67FzsK.js} +1 -1
  219. langflow/frontend/assets/{index-BLXN681C.js → index-ItYiij1i.js} +1 -1
  220. langflow/frontend/assets/{index-CiklyQU3.js → index-IuR_FEdB.js} +1 -1
  221. langflow/frontend/assets/{index-xV6ystWy.js → index-Jj60FQkv.js} +1 -1
  222. langflow/frontend/assets/{index-C_157Mb-.js → index-LlvshmVz.js} +1 -1
  223. langflow/frontend/assets/{index-CDphUsa3.js → index-LwKh3I_W.js} +1 -1
  224. langflow/frontend/assets/{index-BrDz-PxE.js → index-N-xxmKKH.js} +1 -1
  225. langflow/frontend/assets/{index-BsdLyYMY.js → index-RwpaHIAH.js} +1 -1
  226. langflow/frontend/assets/{index-Cu2Xr6_j.js → index-TVvsp-xh.js} +1 -1
  227. langflow/frontend/assets/{index-CPiM2oyj.js → index-TdE2u9zP.js} +1 -1
  228. langflow/frontend/assets/{index-DOj_QWqG.js → index-_x-NkYeW.js} +1 -1
  229. langflow/frontend/assets/{index-YJsAl7vm.js → index-a-YclEbW.js} +1 -1
  230. langflow/frontend/assets/{index-5-CSw2-z.js → index-e9MFKUCo.js} +1 -1
  231. langflow/frontend/assets/{index-BSwBVwyF.js → index-krPr8f2F.js} +1 -1
  232. langflow/frontend/assets/{index-Df6psZEj.js → index-kveiUWuL.js} +1 -1
  233. langflow/frontend/assets/{index-CF4_Og1m.js → index-lE3oSjJi.js} +1 -1
  234. langflow/frontend/assets/{index-C6nzdeYx.js → index-lM3UYg7F.js} +1 -1
  235. langflow/frontend/assets/{index-C-wnbBBY.js → index-nsRk3qgA.js} +1 -1
  236. langflow/frontend/assets/{index-D234yKNJ.js → index-pBO0SZLD.js} +4 -4
  237. langflow/frontend/assets/{index-BMvp94tO.js → index-pbZHsbuE.js} +1 -1
  238. langflow/frontend/assets/{index-hg2y9OAt.js → index-sfX3aWyp.js} +1 -1
  239. langflow/frontend/assets/{index-DTCrijba.js → index-xQz-VJ0-.js} +1 -1
  240. langflow/frontend/assets/{index-SB4rw8D5.js → index-yfcsaHS6.js} +1 -1
  241. langflow/frontend/assets/{index-C-bjC2sz.js → index-zcGjo9fx.js} +1 -1
  242. langflow/frontend/assets/lazyIconImports-BjqDmNYG.js +2 -0
  243. langflow/frontend/assets/{use-post-add-user-JUeLDErC.js → use-post-add-user-w3vpKSOB.js} +1 -1
  244. langflow/frontend/index.html +1 -1
  245. langflow/graph/__init__.py +4 -4
  246. langflow/helpers/data.py +2 -2
  247. langflow/helpers/flow.py +9 -7
  248. langflow/helpers/user.py +2 -2
  249. langflow/initial_setup/setup.py +9 -9
  250. langflow/initial_setup/starter_projects/Basic Prompt Chaining.json +119 -41
  251. langflow/initial_setup/starter_projects/Basic Prompting.json +45 -19
  252. langflow/initial_setup/starter_projects/Blog Writer.json +53 -21
  253. langflow/initial_setup/starter_projects/Custom Component Generator.json +121 -97
  254. langflow/initial_setup/starter_projects/Document Q&A.json +46 -18
  255. langflow/initial_setup/starter_projects/Financial Report Parser.json +49 -17
  256. langflow/initial_setup/starter_projects/Hybrid Search RAG.json +89 -50
  257. langflow/initial_setup/starter_projects/Image Sentiment Analysis.json +86 -22
  258. langflow/initial_setup/starter_projects/Instagram Copywriter.json +210 -57
  259. langflow/initial_setup/starter_projects/Invoice Summarizer.json +132 -35
  260. langflow/initial_setup/starter_projects/Knowledge Ingestion.json +8 -8
  261. langflow/initial_setup/starter_projects/Knowledge Retrieval.json +8 -8
  262. langflow/initial_setup/starter_projects/Market Research.json +174 -48
  263. langflow/initial_setup/starter_projects/Meeting Summary.json +102 -38
  264. langflow/initial_setup/starter_projects/Memory Chatbot.json +49 -21
  265. langflow/initial_setup/starter_projects/News Aggregator.json +140 -39
  266. langflow/initial_setup/starter_projects/Nvidia Remix.json +153 -181
  267. langflow/initial_setup/starter_projects/Pok/303/251dex Agent.json" +132 -35
  268. langflow/initial_setup/starter_projects/Portfolio Website Code Generator.json +106 -43
  269. langflow/initial_setup/starter_projects/Price Deal Finder.json +136 -39
  270. langflow/initial_setup/starter_projects/Research Agent.json +206 -53
  271. langflow/initial_setup/starter_projects/Research Translation Loop.json +66 -34
  272. langflow/initial_setup/starter_projects/SEO Keyword Generator.json +41 -15
  273. langflow/initial_setup/starter_projects/SaaS Pricing.json +128 -31
  274. langflow/initial_setup/starter_projects/Search agent.json +132 -35
  275. langflow/initial_setup/starter_projects/Sequential Tasks Agents.json +422 -98
  276. langflow/initial_setup/starter_projects/Simple Agent.json +150 -42
  277. langflow/initial_setup/starter_projects/Social Media Agent.json +150 -42
  278. langflow/initial_setup/starter_projects/Text Sentiment Analysis.json +120 -24
  279. langflow/initial_setup/starter_projects/Travel Planning Agents.json +418 -94
  280. langflow/initial_setup/starter_projects/Twitter Thread Generator.json +69 -37
  281. langflow/initial_setup/starter_projects/Vector Store RAG.json +66 -38
  282. langflow/initial_setup/starter_projects/Youtube Analysis.json +191 -51
  283. langflow/initial_setup/starter_projects/basic_prompting.py +4 -4
  284. langflow/initial_setup/starter_projects/blog_writer.py +5 -5
  285. langflow/initial_setup/starter_projects/complex_agent.py +8 -8
  286. langflow/initial_setup/starter_projects/document_qa.py +5 -5
  287. langflow/initial_setup/starter_projects/hierarchical_tasks_agent.py +8 -8
  288. langflow/initial_setup/starter_projects/memory_chatbot.py +6 -6
  289. langflow/initial_setup/starter_projects/sequential_tasks_agent.py +7 -7
  290. langflow/initial_setup/starter_projects/vector_store_rag.py +8 -8
  291. langflow/inputs/__init__.py +3 -2
  292. langflow/inputs/constants.py +3 -2
  293. langflow/inputs/input_mixin.py +49 -310
  294. langflow/inputs/inputs.py +72 -703
  295. langflow/inputs/validators.py +2 -18
  296. langflow/interface/__init__.py +4 -0
  297. langflow/interface/components.py +3 -491
  298. langflow/interface/initialize/loading.py +7 -6
  299. langflow/interface/listing.py +3 -25
  300. langflow/interface/run.py +1 -1
  301. langflow/interface/utils.py +3 -111
  302. langflow/io/__init__.py +2 -2
  303. langflow/io/schema.py +11 -302
  304. langflow/load/__init__.py +4 -2
  305. langflow/load/utils.py +2 -96
  306. langflow/logging/__init__.py +2 -1
  307. langflow/logging/setup.py +1 -1
  308. langflow/main.py +8 -5
  309. langflow/memory.py +12 -6
  310. langflow/middleware.py +1 -1
  311. langflow/processing/process.py +7 -7
  312. langflow/schema/__init__.py +22 -5
  313. langflow/schema/artifact.py +1 -1
  314. langflow/schema/data.py +5 -303
  315. langflow/schema/dataframe.py +2 -205
  316. langflow/schema/graph.py +4 -45
  317. langflow/schema/image.py +2 -67
  318. langflow/schema/message.py +6 -470
  319. langflow/schema/playground_events.py +5 -6
  320. langflow/schema/schema.py +24 -117
  321. langflow/serialization/constants.py +3 -2
  322. langflow/serialization/serialization.py +1 -1
  323. langflow/server.py +1 -2
  324. langflow/services/__init__.py +1 -2
  325. langflow/services/auth/mcp_encryption.py +1 -1
  326. langflow/services/auth/service.py +1 -1
  327. langflow/services/auth/utils.py +5 -5
  328. langflow/services/cache/disk.py +2 -2
  329. langflow/services/cache/factory.py +2 -2
  330. langflow/services/cache/service.py +2 -2
  331. langflow/services/cache/utils.py +0 -11
  332. langflow/services/database/factory.py +1 -1
  333. langflow/services/database/models/flow/model.py +1 -1
  334. langflow/services/database/models/message/crud.py +2 -1
  335. langflow/services/database/models/transactions/crud.py +1 -1
  336. langflow/services/database/models/user/crud.py +1 -1
  337. langflow/services/database/service.py +2 -2
  338. langflow/services/database/utils.py +1 -2
  339. langflow/services/deps.py +12 -17
  340. langflow/services/enhanced_manager.py +71 -0
  341. langflow/services/factory.py +14 -7
  342. langflow/services/flow/flow_runner.py +4 -4
  343. langflow/services/job_queue/service.py +2 -1
  344. langflow/services/manager.py +14 -130
  345. langflow/services/schema.py +0 -1
  346. langflow/services/session/service.py +3 -2
  347. langflow/services/settings/__init__.py +0 -3
  348. langflow/services/settings/base.py +16 -549
  349. langflow/services/settings/factory.py +2 -21
  350. langflow/services/settings/feature_flags.py +2 -11
  351. langflow/services/settings/service.py +2 -31
  352. langflow/services/shared_component_cache/factory.py +1 -1
  353. langflow/services/socket/service.py +1 -1
  354. langflow/services/socket/utils.py +1 -8
  355. langflow/services/state/factory.py +1 -1
  356. langflow/services/state/service.py +3 -2
  357. langflow/services/storage/factory.py +2 -2
  358. langflow/services/storage/local.py +1 -2
  359. langflow/services/storage/s3.py +1 -2
  360. langflow/services/storage/service.py +2 -1
  361. langflow/services/store/factory.py +1 -1
  362. langflow/services/store/service.py +2 -2
  363. langflow/services/store/utils.py +1 -2
  364. langflow/services/task/service.py +2 -1
  365. langflow/services/task/temp_flow_cleanup.py +1 -1
  366. langflow/services/telemetry/factory.py +1 -1
  367. langflow/services/telemetry/service.py +2 -3
  368. langflow/services/tracing/arize_phoenix.py +3 -3
  369. langflow/services/tracing/base.py +1 -1
  370. langflow/services/tracing/factory.py +1 -1
  371. langflow/services/tracing/langfuse.py +2 -2
  372. langflow/services/tracing/langsmith.py +2 -2
  373. langflow/services/tracing/langwatch.py +4 -4
  374. langflow/services/tracing/opik.py +2 -2
  375. langflow/services/tracing/service.py +17 -11
  376. langflow/services/tracing/traceloop.py +2 -2
  377. langflow/services/tracing/utils.py +1 -1
  378. langflow/services/utils.py +54 -9
  379. langflow/services/variable/factory.py +1 -1
  380. langflow/services/variable/kubernetes.py +2 -3
  381. langflow/services/variable/kubernetes_secrets.py +1 -2
  382. langflow/services/variable/service.py +2 -3
  383. langflow/template/__init__.py +2 -9
  384. langflow/template/field/__init__.py +3 -0
  385. langflow/template/field/base.py +2 -256
  386. langflow/template/frontend_node.py +3 -0
  387. langflow/template/utils.py +2 -216
  388. langflow/utils/constants.py +28 -204
  389. langflow/utils/lazy_load.py +3 -14
  390. langflow/utils/schemas.py +2 -3
  391. langflow/utils/template_validation.py +2 -2
  392. langflow/utils/util.py +59 -479
  393. langflow/utils/validate.py +2 -488
  394. langflow/utils/voice_utils.py +1 -2
  395. langflow/worker.py +1 -1
  396. {langflow_base_nightly-0.5.1.dev2.dist-info → langflow_base_nightly-0.5.1.dev4.dist-info}/METADATA +2 -1
  397. langflow_base_nightly-0.5.1.dev4.dist-info/RECORD +633 -0
  398. langflow/base/agents/agent.py +0 -267
  399. langflow/base/agents/callback.py +0 -130
  400. langflow/base/agents/context.py +0 -109
  401. langflow/base/agents/crewai/__init__.py +0 -0
  402. langflow/base/agents/crewai/crew.py +0 -231
  403. langflow/base/agents/crewai/tasks.py +0 -12
  404. langflow/base/agents/default_prompts.py +0 -23
  405. langflow/base/agents/errors.py +0 -15
  406. langflow/base/agents/events.py +0 -346
  407. langflow/base/agents/utils.py +0 -205
  408. langflow/base/astra_assistants/__init__.py +0 -0
  409. langflow/base/astra_assistants/util.py +0 -171
  410. langflow/base/chains/__init__.py +0 -0
  411. langflow/base/chains/model.py +0 -19
  412. langflow/base/composio/__init__.py +0 -0
  413. langflow/base/composio/composio_base.py +0 -1297
  414. langflow/base/compressors/__init__.py +0 -0
  415. langflow/base/compressors/model.py +0 -60
  416. langflow/base/constants.py +0 -46
  417. langflow/base/curl/__init__.py +0 -0
  418. langflow/base/curl/parse.py +0 -188
  419. langflow/base/data/base_file.py +0 -685
  420. langflow/base/data/docling_utils.py +0 -245
  421. langflow/base/document_transformers/__init__.py +0 -0
  422. langflow/base/document_transformers/model.py +0 -43
  423. langflow/base/embeddings/aiml_embeddings.py +0 -62
  424. langflow/base/embeddings/model.py +0 -26
  425. langflow/base/flow_processing/__init__.py +0 -0
  426. langflow/base/flow_processing/utils.py +0 -86
  427. langflow/base/huggingface/__init__.py +0 -0
  428. langflow/base/huggingface/model_bridge.py +0 -133
  429. langflow/base/langchain_utilities/__init__.py +0 -0
  430. langflow/base/langchain_utilities/model.py +0 -35
  431. langflow/base/langchain_utilities/spider_constants.py +0 -1
  432. langflow/base/langwatch/__init__.py +0 -0
  433. langflow/base/langwatch/utils.py +0 -18
  434. langflow/base/mcp/__init__.py +0 -0
  435. langflow/base/mcp/constants.py +0 -2
  436. langflow/base/mcp/util.py +0 -1524
  437. langflow/base/memory/memory.py +0 -49
  438. langflow/base/memory/model.py +0 -38
  439. langflow/base/models/aiml_constants.py +0 -51
  440. langflow/base/models/anthropic_constants.py +0 -47
  441. langflow/base/models/aws_constants.py +0 -151
  442. langflow/base/models/chat_result.py +0 -76
  443. langflow/base/models/google_generative_ai_constants.py +0 -70
  444. langflow/base/models/groq_constants.py +0 -134
  445. langflow/base/models/model.py +0 -375
  446. langflow/base/models/model_input_constants.py +0 -299
  447. langflow/base/models/model_metadata.py +0 -41
  448. langflow/base/models/model_utils.py +0 -8
  449. langflow/base/models/novita_constants.py +0 -35
  450. langflow/base/models/ollama_constants.py +0 -49
  451. langflow/base/models/sambanova_constants.py +0 -18
  452. langflow/base/processing/__init__.py +0 -0
  453. langflow/base/prompts/utils.py +0 -61
  454. langflow/base/textsplitters/model.py +0 -28
  455. langflow/base/tools/base.py +0 -26
  456. langflow/base/tools/component_tool.py +0 -324
  457. langflow/base/tools/constants.py +0 -49
  458. langflow/base/tools/flow_tool.py +0 -131
  459. langflow/base/tools/run_flow.py +0 -227
  460. langflow/base/vectorstores/model.py +0 -193
  461. langflow/base/vectorstores/utils.py +0 -22
  462. langflow/base/vectorstores/vector_store_connection_decorator.py +0 -52
  463. langflow/components/FAISS/__init__.py +0 -34
  464. langflow/components/FAISS/faiss.py +0 -111
  465. langflow/components/Notion/__init__.py +0 -19
  466. langflow/components/Notion/add_content_to_page.py +0 -269
  467. langflow/components/Notion/create_page.py +0 -94
  468. langflow/components/Notion/list_database_properties.py +0 -68
  469. langflow/components/Notion/list_pages.py +0 -122
  470. langflow/components/Notion/list_users.py +0 -77
  471. langflow/components/Notion/page_content_viewer.py +0 -93
  472. langflow/components/Notion/search.py +0 -111
  473. langflow/components/Notion/update_page_property.py +0 -114
  474. langflow/components/_importing.py +0 -37
  475. langflow/components/agentql/__init__.py +0 -3
  476. langflow/components/agentql/agentql_api.py +0 -151
  477. langflow/components/agents/__init__.py +0 -4
  478. langflow/components/agents/agent.py +0 -554
  479. langflow/components/agents/mcp_component.py +0 -501
  480. langflow/components/aiml/__init__.py +0 -37
  481. langflow/components/aiml/aiml.py +0 -112
  482. langflow/components/aiml/aiml_embeddings.py +0 -37
  483. langflow/components/amazon/__init__.py +0 -36
  484. langflow/components/amazon/amazon_bedrock_embedding.py +0 -109
  485. langflow/components/amazon/amazon_bedrock_model.py +0 -124
  486. langflow/components/amazon/s3_bucket_uploader.py +0 -211
  487. langflow/components/anthropic/__init__.py +0 -34
  488. langflow/components/anthropic/anthropic.py +0 -187
  489. langflow/components/apify/__init__.py +0 -5
  490. langflow/components/apify/apify_actor.py +0 -325
  491. langflow/components/arxiv/__init__.py +0 -3
  492. langflow/components/arxiv/arxiv.py +0 -163
  493. langflow/components/assemblyai/__init__.py +0 -46
  494. langflow/components/assemblyai/assemblyai_get_subtitles.py +0 -83
  495. langflow/components/assemblyai/assemblyai_lemur.py +0 -183
  496. langflow/components/assemblyai/assemblyai_list_transcripts.py +0 -95
  497. langflow/components/assemblyai/assemblyai_poll_transcript.py +0 -72
  498. langflow/components/assemblyai/assemblyai_start_transcript.py +0 -188
  499. langflow/components/azure/__init__.py +0 -37
  500. langflow/components/azure/azure_openai.py +0 -95
  501. langflow/components/azure/azure_openai_embeddings.py +0 -83
  502. langflow/components/baidu/__init__.py +0 -32
  503. langflow/components/baidu/baidu_qianfan_chat.py +0 -113
  504. langflow/components/bing/__init__.py +0 -3
  505. langflow/components/bing/bing_search_api.py +0 -61
  506. langflow/components/cassandra/__init__.py +0 -40
  507. langflow/components/cassandra/cassandra.py +0 -264
  508. langflow/components/cassandra/cassandra_chat.py +0 -92
  509. langflow/components/cassandra/cassandra_graph.py +0 -238
  510. langflow/components/chains/__init__.py +0 -0
  511. langflow/components/chroma/__init__.py +0 -34
  512. langflow/components/chroma/chroma.py +0 -167
  513. langflow/components/cleanlab/__init__.py +0 -40
  514. langflow/components/cleanlab/cleanlab_evaluator.py +0 -157
  515. langflow/components/cleanlab/cleanlab_rag_evaluator.py +0 -254
  516. langflow/components/cleanlab/cleanlab_remediator.py +0 -131
  517. langflow/components/clickhouse/__init__.py +0 -34
  518. langflow/components/clickhouse/clickhouse.py +0 -135
  519. langflow/components/cloudflare/__init__.py +0 -32
  520. langflow/components/cloudflare/cloudflare.py +0 -81
  521. langflow/components/cohere/__init__.py +0 -40
  522. langflow/components/cohere/cohere_embeddings.py +0 -81
  523. langflow/components/cohere/cohere_models.py +0 -46
  524. langflow/components/cohere/cohere_rerank.py +0 -51
  525. langflow/components/composio/__init__.py +0 -73
  526. langflow/components/composio/composio_api.py +0 -268
  527. langflow/components/composio/dropbox_compnent.py +0 -11
  528. langflow/components/composio/github_composio.py +0 -11
  529. langflow/components/composio/gmail_composio.py +0 -38
  530. langflow/components/composio/googlecalendar_composio.py +0 -11
  531. langflow/components/composio/googlemeet_composio.py +0 -11
  532. langflow/components/composio/googletasks_composio.py +0 -8
  533. langflow/components/composio/linear_composio.py +0 -11
  534. langflow/components/composio/outlook_composio.py +0 -11
  535. langflow/components/composio/reddit_composio.py +0 -11
  536. langflow/components/composio/slack_composio.py +0 -11
  537. langflow/components/composio/slackbot_composio.py +0 -11
  538. langflow/components/composio/supabase_composio.py +0 -11
  539. langflow/components/composio/todoist_composio.py +0 -11
  540. langflow/components/composio/youtube_composio.py +0 -11
  541. langflow/components/confluence/__init__.py +0 -3
  542. langflow/components/confluence/confluence.py +0 -84
  543. langflow/components/couchbase/__init__.py +0 -34
  544. langflow/components/couchbase/couchbase.py +0 -102
  545. langflow/components/crewai/__init__.py +0 -49
  546. langflow/components/crewai/crewai.py +0 -107
  547. langflow/components/crewai/hierarchical_crew.py +0 -46
  548. langflow/components/crewai/hierarchical_task.py +0 -44
  549. langflow/components/crewai/sequential_crew.py +0 -52
  550. langflow/components/crewai/sequential_task.py +0 -73
  551. langflow/components/crewai/sequential_task_agent.py +0 -143
  552. langflow/components/custom_component/__init__.py +0 -34
  553. langflow/components/custom_component/custom_component.py +0 -31
  554. langflow/components/data/__init__.py +0 -25
  555. langflow/components/data/api_request.py +0 -545
  556. langflow/components/data/csv_to_data.py +0 -95
  557. langflow/components/data/directory.py +0 -113
  558. langflow/components/data/file.py +0 -586
  559. langflow/components/data/json_to_data.py +0 -98
  560. langflow/components/data/news_search.py +0 -164
  561. langflow/components/data/rss.py +0 -69
  562. langflow/components/data/sql_executor.py +0 -99
  563. langflow/components/data/url.py +0 -299
  564. langflow/components/data/web_search.py +0 -112
  565. langflow/components/data/webhook.py +0 -56
  566. langflow/components/datastax/__init__.py +0 -70
  567. langflow/components/datastax/astra_assistant_manager.py +0 -306
  568. langflow/components/datastax/astra_db.py +0 -69
  569. langflow/components/datastax/astra_vectorize.py +0 -124
  570. langflow/components/datastax/astradb_cql.py +0 -314
  571. langflow/components/datastax/astradb_graph.py +0 -319
  572. langflow/components/datastax/astradb_tool.py +0 -414
  573. langflow/components/datastax/astradb_vectorstore.py +0 -1285
  574. langflow/components/datastax/create_assistant.py +0 -58
  575. langflow/components/datastax/create_thread.py +0 -32
  576. langflow/components/datastax/dotenv.py +0 -35
  577. langflow/components/datastax/get_assistant.py +0 -37
  578. langflow/components/datastax/getenvvar.py +0 -30
  579. langflow/components/datastax/graph_rag.py +0 -141
  580. langflow/components/datastax/hcd.py +0 -314
  581. langflow/components/datastax/list_assistants.py +0 -25
  582. langflow/components/datastax/run.py +0 -89
  583. langflow/components/deactivated/__init__.py +0 -19
  584. langflow/components/deactivated/amazon_kendra.py +0 -66
  585. langflow/components/deactivated/chat_litellm_model.py +0 -158
  586. langflow/components/deactivated/code_block_extractor.py +0 -26
  587. langflow/components/deactivated/documents_to_data.py +0 -22
  588. langflow/components/deactivated/embed.py +0 -16
  589. langflow/components/deactivated/extract_key_from_data.py +0 -46
  590. langflow/components/deactivated/json_document_builder.py +0 -59
  591. langflow/components/deactivated/list_flows.py +0 -20
  592. langflow/components/deactivated/mcp_sse.py +0 -61
  593. langflow/components/deactivated/mcp_stdio.py +0 -62
  594. langflow/components/deactivated/merge_data.py +0 -93
  595. langflow/components/deactivated/message.py +0 -37
  596. langflow/components/deactivated/metal.py +0 -54
  597. langflow/components/deactivated/multi_query.py +0 -59
  598. langflow/components/deactivated/retriever.py +0 -43
  599. langflow/components/deactivated/selective_passthrough.py +0 -77
  600. langflow/components/deactivated/should_run_next.py +0 -40
  601. langflow/components/deactivated/split_text.py +0 -63
  602. langflow/components/deactivated/store_message.py +0 -24
  603. langflow/components/deactivated/sub_flow.py +0 -124
  604. langflow/components/deactivated/vectara_self_query.py +0 -76
  605. langflow/components/deactivated/vector_store.py +0 -24
  606. langflow/components/deepseek/__init__.py +0 -34
  607. langflow/components/deepseek/deepseek.py +0 -136
  608. langflow/components/docling/__init__.py +0 -43
  609. langflow/components/docling/chunk_docling_document.py +0 -186
  610. langflow/components/docling/docling_inline.py +0 -235
  611. langflow/components/docling/docling_remote.py +0 -193
  612. langflow/components/docling/export_docling_document.py +0 -117
  613. langflow/components/documentloaders/__init__.py +0 -0
  614. langflow/components/duckduckgo/__init__.py +0 -3
  615. langflow/components/duckduckgo/duck_duck_go_search_run.py +0 -92
  616. langflow/components/elastic/__init__.py +0 -37
  617. langflow/components/elastic/elasticsearch.py +0 -267
  618. langflow/components/elastic/opensearch.py +0 -243
  619. langflow/components/embeddings/__init__.py +0 -37
  620. langflow/components/embeddings/similarity.py +0 -76
  621. langflow/components/embeddings/text_embedder.py +0 -64
  622. langflow/components/exa/__init__.py +0 -3
  623. langflow/components/exa/exa_search.py +0 -68
  624. langflow/components/firecrawl/__init__.py +0 -43
  625. langflow/components/firecrawl/firecrawl_crawl_api.py +0 -88
  626. langflow/components/firecrawl/firecrawl_extract_api.py +0 -136
  627. langflow/components/firecrawl/firecrawl_map_api.py +0 -89
  628. langflow/components/firecrawl/firecrawl_scrape_api.py +0 -73
  629. langflow/components/git/__init__.py +0 -4
  630. langflow/components/git/git.py +0 -262
  631. langflow/components/git/gitextractor.py +0 -196
  632. langflow/components/glean/__init__.py +0 -3
  633. langflow/components/glean/glean_search_api.py +0 -173
  634. langflow/components/google/__init__.py +0 -17
  635. langflow/components/google/gmail.py +0 -192
  636. langflow/components/google/google_bq_sql_executor.py +0 -157
  637. langflow/components/google/google_drive.py +0 -92
  638. langflow/components/google/google_drive_search.py +0 -152
  639. langflow/components/google/google_generative_ai.py +0 -147
  640. langflow/components/google/google_generative_ai_embeddings.py +0 -141
  641. langflow/components/google/google_oauth_token.py +0 -89
  642. langflow/components/google/google_search_api_core.py +0 -68
  643. langflow/components/google/google_serper_api_core.py +0 -74
  644. langflow/components/groq/__init__.py +0 -34
  645. langflow/components/groq/groq.py +0 -140
  646. langflow/components/helpers/__init__.py +0 -52
  647. langflow/components/helpers/calculator_core.py +0 -89
  648. langflow/components/helpers/create_list.py +0 -40
  649. langflow/components/helpers/current_date.py +0 -42
  650. langflow/components/helpers/id_generator.py +0 -42
  651. langflow/components/helpers/memory.py +0 -251
  652. langflow/components/helpers/output_parser.py +0 -45
  653. langflow/components/helpers/store_message.py +0 -90
  654. langflow/components/homeassistant/__init__.py +0 -7
  655. langflow/components/homeassistant/home_assistant_control.py +0 -152
  656. langflow/components/homeassistant/list_home_assistant_states.py +0 -137
  657. langflow/components/huggingface/__init__.py +0 -37
  658. langflow/components/huggingface/huggingface.py +0 -197
  659. langflow/components/huggingface/huggingface_inference_api.py +0 -106
  660. langflow/components/ibm/__init__.py +0 -34
  661. langflow/components/ibm/watsonx.py +0 -203
  662. langflow/components/ibm/watsonx_embeddings.py +0 -135
  663. langflow/components/icosacomputing/__init__.py +0 -5
  664. langflow/components/icosacomputing/combinatorial_reasoner.py +0 -84
  665. langflow/components/input_output/__init__.py +0 -38
  666. langflow/components/input_output/chat.py +0 -120
  667. langflow/components/input_output/chat_output.py +0 -200
  668. langflow/components/input_output/text.py +0 -27
  669. langflow/components/input_output/text_output.py +0 -29
  670. langflow/components/jigsawstack/__init__.py +0 -23
  671. langflow/components/jigsawstack/ai_scrape.py +0 -126
  672. langflow/components/jigsawstack/ai_web_search.py +0 -136
  673. langflow/components/jigsawstack/file_read.py +0 -115
  674. langflow/components/jigsawstack/file_upload.py +0 -94
  675. langflow/components/jigsawstack/image_generation.py +0 -205
  676. langflow/components/jigsawstack/nsfw.py +0 -60
  677. langflow/components/jigsawstack/object_detection.py +0 -124
  678. langflow/components/jigsawstack/sentiment.py +0 -112
  679. langflow/components/jigsawstack/text_to_sql.py +0 -90
  680. langflow/components/jigsawstack/text_translate.py +0 -77
  681. langflow/components/jigsawstack/vocr.py +0 -107
  682. langflow/components/langchain_utilities/__init__.py +0 -109
  683. langflow/components/langchain_utilities/character.py +0 -53
  684. langflow/components/langchain_utilities/conversation.py +0 -52
  685. langflow/components/langchain_utilities/csv_agent.py +0 -107
  686. langflow/components/langchain_utilities/fake_embeddings.py +0 -26
  687. langflow/components/langchain_utilities/html_link_extractor.py +0 -35
  688. langflow/components/langchain_utilities/json_agent.py +0 -45
  689. langflow/components/langchain_utilities/langchain_hub.py +0 -126
  690. langflow/components/langchain_utilities/language_recursive.py +0 -49
  691. langflow/components/langchain_utilities/language_semantic.py +0 -138
  692. langflow/components/langchain_utilities/llm_checker.py +0 -39
  693. langflow/components/langchain_utilities/llm_math.py +0 -42
  694. langflow/components/langchain_utilities/natural_language.py +0 -61
  695. langflow/components/langchain_utilities/openai_tools.py +0 -53
  696. langflow/components/langchain_utilities/openapi.py +0 -48
  697. langflow/components/langchain_utilities/recursive_character.py +0 -60
  698. langflow/components/langchain_utilities/retrieval_qa.py +0 -83
  699. langflow/components/langchain_utilities/runnable_executor.py +0 -137
  700. langflow/components/langchain_utilities/self_query.py +0 -80
  701. langflow/components/langchain_utilities/spider.py +0 -142
  702. langflow/components/langchain_utilities/sql.py +0 -40
  703. langflow/components/langchain_utilities/sql_database.py +0 -35
  704. langflow/components/langchain_utilities/sql_generator.py +0 -78
  705. langflow/components/langchain_utilities/tool_calling.py +0 -59
  706. langflow/components/langchain_utilities/vector_store_info.py +0 -49
  707. langflow/components/langchain_utilities/vector_store_router.py +0 -33
  708. langflow/components/langchain_utilities/xml_agent.py +0 -71
  709. langflow/components/langwatch/__init__.py +0 -3
  710. langflow/components/langwatch/langwatch.py +0 -278
  711. langflow/components/link_extractors/__init__.py +0 -0
  712. langflow/components/lmstudio/__init__.py +0 -34
  713. langflow/components/lmstudio/lmstudioembeddings.py +0 -89
  714. langflow/components/lmstudio/lmstudiomodel.py +0 -129
  715. langflow/components/logic/__init__.py +0 -52
  716. langflow/components/logic/conditional_router.py +0 -171
  717. langflow/components/logic/data_conditional_router.py +0 -125
  718. langflow/components/logic/flow_tool.py +0 -110
  719. langflow/components/logic/listen.py +0 -29
  720. langflow/components/logic/loop.py +0 -125
  721. langflow/components/logic/notify.py +0 -88
  722. langflow/components/logic/pass_message.py +0 -35
  723. langflow/components/logic/run_flow.py +0 -71
  724. langflow/components/logic/sub_flow.py +0 -114
  725. langflow/components/maritalk/__init__.py +0 -32
  726. langflow/components/maritalk/maritalk.py +0 -52
  727. langflow/components/mem0/__init__.py +0 -3
  728. langflow/components/mem0/mem0_chat_memory.py +0 -136
  729. langflow/components/milvus/__init__.py +0 -34
  730. langflow/components/milvus/milvus.py +0 -115
  731. langflow/components/mistral/__init__.py +0 -37
  732. langflow/components/mistral/mistral.py +0 -114
  733. langflow/components/mistral/mistral_embeddings.py +0 -58
  734. langflow/components/models/__init__.py +0 -34
  735. langflow/components/models/embedding_model.py +0 -114
  736. langflow/components/models/language_model.py +0 -144
  737. langflow/components/mongodb/__init__.py +0 -34
  738. langflow/components/mongodb/mongodb_atlas.py +0 -213
  739. langflow/components/needle/__init__.py +0 -3
  740. langflow/components/needle/needle.py +0 -104
  741. langflow/components/notdiamond/__init__.py +0 -36
  742. langflow/components/notdiamond/notdiamond.py +0 -228
  743. langflow/components/novita/__init__.py +0 -32
  744. langflow/components/novita/novita.py +0 -130
  745. langflow/components/nvidia/__init__.py +0 -57
  746. langflow/components/nvidia/nvidia.py +0 -157
  747. langflow/components/nvidia/nvidia_embedding.py +0 -77
  748. langflow/components/nvidia/nvidia_ingest.py +0 -317
  749. langflow/components/nvidia/nvidia_rerank.py +0 -63
  750. langflow/components/nvidia/system_assist.py +0 -65
  751. langflow/components/olivya/__init__.py +0 -3
  752. langflow/components/olivya/olivya.py +0 -116
  753. langflow/components/ollama/__init__.py +0 -37
  754. langflow/components/ollama/ollama.py +0 -330
  755. langflow/components/ollama/ollama_embeddings.py +0 -106
  756. langflow/components/openai/__init__.py +0 -37
  757. langflow/components/openai/openai.py +0 -100
  758. langflow/components/openai/openai_chat_model.py +0 -158
  759. langflow/components/openrouter/__init__.py +0 -32
  760. langflow/components/openrouter/openrouter.py +0 -202
  761. langflow/components/output_parsers/__init__.py +0 -0
  762. langflow/components/perplexity/__init__.py +0 -34
  763. langflow/components/perplexity/perplexity.py +0 -75
  764. langflow/components/pgvector/__init__.py +0 -34
  765. langflow/components/pgvector/pgvector.py +0 -72
  766. langflow/components/pinecone/__init__.py +0 -34
  767. langflow/components/pinecone/pinecone.py +0 -134
  768. langflow/components/processing/alter_metadata.py +0 -108
  769. langflow/components/processing/batch_run.py +0 -205
  770. langflow/components/processing/combine_text.py +0 -39
  771. langflow/components/processing/create_data.py +0 -110
  772. langflow/components/processing/data_operations.py +0 -438
  773. langflow/components/processing/data_to_dataframe.py +0 -70
  774. langflow/components/processing/dataframe_operations.py +0 -321
  775. langflow/components/processing/extract_key.py +0 -53
  776. langflow/components/processing/filter_data.py +0 -42
  777. langflow/components/processing/filter_data_values.py +0 -88
  778. langflow/components/processing/json_cleaner.py +0 -103
  779. langflow/components/processing/lambda_filter.py +0 -154
  780. langflow/components/processing/llm_router.py +0 -499
  781. langflow/components/processing/merge_data.py +0 -90
  782. langflow/components/processing/message_to_data.py +0 -36
  783. langflow/components/processing/parse_data.py +0 -70
  784. langflow/components/processing/parse_dataframe.py +0 -68
  785. langflow/components/processing/parse_json_data.py +0 -90
  786. langflow/components/processing/parser.py +0 -143
  787. langflow/components/processing/prompt.py +0 -67
  788. langflow/components/processing/python_repl_core.py +0 -98
  789. langflow/components/processing/regex.py +0 -82
  790. langflow/components/processing/save_file.py +0 -208
  791. langflow/components/processing/select_data.py +0 -48
  792. langflow/components/processing/split_text.py +0 -141
  793. langflow/components/processing/structured_output.py +0 -202
  794. langflow/components/processing/update_data.py +0 -160
  795. langflow/components/prototypes/__init__.py +0 -34
  796. langflow/components/prototypes/python_function.py +0 -73
  797. langflow/components/qdrant/__init__.py +0 -34
  798. langflow/components/qdrant/qdrant.py +0 -109
  799. langflow/components/redis/__init__.py +0 -37
  800. langflow/components/redis/redis.py +0 -89
  801. langflow/components/redis/redis_chat.py +0 -43
  802. langflow/components/sambanova/__init__.py +0 -32
  803. langflow/components/sambanova/sambanova.py +0 -84
  804. langflow/components/scrapegraph/__init__.py +0 -40
  805. langflow/components/scrapegraph/scrapegraph_markdownify_api.py +0 -64
  806. langflow/components/scrapegraph/scrapegraph_search_api.py +0 -64
  807. langflow/components/scrapegraph/scrapegraph_smart_scraper_api.py +0 -71
  808. langflow/components/searchapi/__init__.py +0 -36
  809. langflow/components/searchapi/search.py +0 -79
  810. langflow/components/serpapi/__init__.py +0 -3
  811. langflow/components/serpapi/serp.py +0 -115
  812. langflow/components/serper/__init__.py +0 -3
  813. langflow/components/serper/google_serper_api_core.py +0 -74
  814. langflow/components/supabase/__init__.py +0 -37
  815. langflow/components/supabase/supabase.py +0 -76
  816. langflow/components/tavily/__init__.py +0 -4
  817. langflow/components/tavily/tavily_extract.py +0 -117
  818. langflow/components/tavily/tavily_search.py +0 -212
  819. langflow/components/textsplitters/__init__.py +0 -0
  820. langflow/components/toolkits/__init__.py +0 -0
  821. langflow/components/tools/__init__.py +0 -72
  822. langflow/components/tools/calculator.py +0 -103
  823. langflow/components/tools/google_search_api.py +0 -45
  824. langflow/components/tools/google_serper_api.py +0 -115
  825. langflow/components/tools/python_code_structured_tool.py +0 -327
  826. langflow/components/tools/python_repl.py +0 -97
  827. langflow/components/tools/search_api.py +0 -87
  828. langflow/components/tools/searxng.py +0 -145
  829. langflow/components/tools/serp_api.py +0 -119
  830. langflow/components/tools/tavily_search_tool.py +0 -344
  831. langflow/components/tools/wikidata_api.py +0 -102
  832. langflow/components/tools/wikipedia_api.py +0 -49
  833. langflow/components/tools/yahoo_finance.py +0 -124
  834. langflow/components/twelvelabs/__init__.py +0 -52
  835. langflow/components/twelvelabs/convert_astra_results.py +0 -84
  836. langflow/components/twelvelabs/pegasus_index.py +0 -311
  837. langflow/components/twelvelabs/split_video.py +0 -291
  838. langflow/components/twelvelabs/text_embeddings.py +0 -57
  839. langflow/components/twelvelabs/twelvelabs_pegasus.py +0 -408
  840. langflow/components/twelvelabs/video_embeddings.py +0 -100
  841. langflow/components/twelvelabs/video_file.py +0 -179
  842. langflow/components/unstructured/__init__.py +0 -3
  843. langflow/components/unstructured/unstructured.py +0 -121
  844. langflow/components/upstash/__init__.py +0 -34
  845. langflow/components/upstash/upstash.py +0 -124
  846. langflow/components/vectara/__init__.py +0 -37
  847. langflow/components/vectara/vectara.py +0 -97
  848. langflow/components/vectara/vectara_rag.py +0 -164
  849. langflow/components/vectorstores/__init__.py +0 -34
  850. langflow/components/vectorstores/local_db.py +0 -261
  851. langflow/components/vertexai/__init__.py +0 -37
  852. langflow/components/vertexai/vertexai.py +0 -71
  853. langflow/components/vertexai/vertexai_embeddings.py +0 -67
  854. langflow/components/weaviate/__init__.py +0 -34
  855. langflow/components/weaviate/weaviate.py +0 -89
  856. langflow/components/wikipedia/__init__.py +0 -4
  857. langflow/components/wikipedia/wikidata.py +0 -86
  858. langflow/components/wikipedia/wikipedia.py +0 -53
  859. langflow/components/wolframalpha/__init__.py +0 -3
  860. langflow/components/wolframalpha/wolfram_alpha_api.py +0 -54
  861. langflow/components/xai/__init__.py +0 -32
  862. langflow/components/xai/xai.py +0 -167
  863. langflow/components/yahoosearch/__init__.py +0 -3
  864. langflow/components/yahoosearch/yahoo.py +0 -137
  865. langflow/components/youtube/__init__.py +0 -52
  866. langflow/components/youtube/channel.py +0 -227
  867. langflow/components/youtube/comments.py +0 -231
  868. langflow/components/youtube/playlist.py +0 -33
  869. langflow/components/youtube/search.py +0 -120
  870. langflow/components/youtube/trending.py +0 -285
  871. langflow/components/youtube/video_details.py +0 -263
  872. langflow/components/youtube/youtube_transcripts.py +0 -118
  873. langflow/components/zep/__init__.py +0 -3
  874. langflow/components/zep/zep.py +0 -44
  875. langflow/custom/attributes.py +0 -86
  876. langflow/custom/code_parser/__init__.py +0 -3
  877. langflow/custom/code_parser/code_parser.py +0 -361
  878. langflow/custom/custom_component/base_component.py +0 -118
  879. langflow/custom/dependency_analyzer.py +0 -165
  880. langflow/custom/directory_reader/__init__.py +0 -3
  881. langflow/custom/directory_reader/directory_reader.py +0 -359
  882. langflow/custom/directory_reader/utils.py +0 -171
  883. langflow/custom/eval.py +0 -12
  884. langflow/custom/schema.py +0 -32
  885. langflow/custom/tree_visitor.py +0 -21
  886. langflow/frontend/assets/lazyIconImports-Ci-S9xBA.js +0 -2
  887. langflow/graph/edge/__init__.py +0 -0
  888. langflow/graph/edge/base.py +0 -277
  889. langflow/graph/edge/schema.py +0 -119
  890. langflow/graph/edge/utils.py +0 -0
  891. langflow/graph/graph/__init__.py +0 -0
  892. langflow/graph/graph/ascii.py +0 -202
  893. langflow/graph/graph/base.py +0 -2185
  894. langflow/graph/graph/constants.py +0 -58
  895. langflow/graph/graph/runnable_vertices_manager.py +0 -133
  896. langflow/graph/graph/schema.py +0 -53
  897. langflow/graph/graph/state_model.py +0 -66
  898. langflow/graph/graph/utils.py +0 -1024
  899. langflow/graph/schema.py +0 -75
  900. langflow/graph/state/__init__.py +0 -0
  901. langflow/graph/state/model.py +0 -237
  902. langflow/graph/utils.py +0 -229
  903. langflow/graph/vertex/__init__.py +0 -0
  904. langflow/graph/vertex/base.py +0 -811
  905. langflow/graph/vertex/constants.py +0 -0
  906. langflow/graph/vertex/exceptions.py +0 -4
  907. langflow/graph/vertex/param_handler.py +0 -255
  908. langflow/graph/vertex/schema.py +0 -26
  909. langflow/graph/vertex/utils.py +0 -19
  910. langflow/graph/vertex/vertex_types.py +0 -489
  911. langflow/legacy_custom/__init__.py +0 -0
  912. langflow/legacy_custom/customs.py +0 -16
  913. langflow/load/load.py +0 -250
  914. langflow/logging/logger.py +0 -369
  915. langflow/processing/utils.py +0 -25
  916. langflow/schema/openai_responses_schemas.py +0 -74
  917. langflow/schema/serialize.py +0 -13
  918. langflow/services/chat/config.py +0 -2
  919. langflow/services/settings/auth.py +0 -130
  920. langflow/services/settings/constants.py +0 -31
  921. langflow/services/settings/manager.py +0 -49
  922. langflow/services/settings/utils.py +0 -40
  923. langflow/template/field/prompt.py +0 -2
  924. langflow/template/frontend_node/__init__.py +0 -6
  925. langflow/template/frontend_node/base.py +0 -212
  926. langflow/template/frontend_node/constants.py +0 -65
  927. langflow/template/frontend_node/custom_components.py +0 -97
  928. langflow/template/template/__init__.py +0 -0
  929. langflow/template/template/base.py +0 -99
  930. langflow/utils/async_helpers.py +0 -42
  931. langflow/utils/concurrency.py +0 -60
  932. langflow/utils/util_strings.py +0 -56
  933. langflow_base_nightly-0.5.1.dev2.dist-info/RECORD +0 -1159
  934. {langflow_base_nightly-0.5.1.dev2.dist-info → langflow_base_nightly-0.5.1.dev4.dist-info}/WHEEL +0 -0
  935. {langflow_base_nightly-0.5.1.dev2.dist-info → langflow_base_nightly-0.5.1.dev4.dist-info}/entry_points.txt +0 -0
langflow/base/mcp/util.py DELETED
@@ -1,1524 +0,0 @@
1
- import asyncio
2
- import contextlib
3
- import inspect
4
- import os
5
- import platform
6
- import re
7
- import shutil
8
- import unicodedata
9
- from collections.abc import Awaitable, Callable
10
- from typing import Any
11
- from urllib.parse import urlparse
12
- from uuid import UUID
13
-
14
- import httpx
15
- from anyio import ClosedResourceError
16
- from httpx import codes as httpx_codes
17
- from langchain_core.tools import StructuredTool
18
- from mcp import ClientSession
19
- from mcp.shared.exceptions import McpError
20
- from pydantic import BaseModel, Field, create_model
21
- from sqlmodel import select
22
-
23
- from langflow.logging.logger import logger
24
- from langflow.services.database.models.flow.model import Flow
25
- from langflow.services.deps import get_settings_service
26
-
27
- HTTP_ERROR_STATUS_CODE = httpx_codes.BAD_REQUEST # HTTP status code for client errors
28
- NULLABLE_TYPE_LENGTH = 2 # Number of types in a nullable union (the type itself + null)
29
-
30
- # HTTP status codes used in validation
31
- HTTP_NOT_FOUND = 404
32
- HTTP_BAD_REQUEST = 400
33
- HTTP_INTERNAL_SERVER_ERROR = 500
34
-
35
- # MCP Session Manager constants
36
- settings = get_settings_service().settings
37
- MAX_SESSIONS_PER_SERVER = (
38
- settings.mcp_max_sessions_per_server
39
- ) # Maximum number of sessions per server to prevent resource exhaustion
40
- SESSION_IDLE_TIMEOUT = settings.mcp_session_idle_timeout # 5 minutes idle timeout for sessions
41
- SESSION_CLEANUP_INTERVAL = settings.mcp_session_cleanup_interval # Cleanup interval in seconds
42
- # RFC 7230 compliant header name pattern: token = 1*tchar
43
- # tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." /
44
- # "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA
45
- HEADER_NAME_PATTERN = re.compile(r"^[!#$%&\'*+\-.0-9A-Z^_`a-z|~]+$")
46
-
47
- # Common allowed headers for MCP connections
48
- ALLOWED_HEADERS = {
49
- "authorization",
50
- "accept",
51
- "accept-encoding",
52
- "accept-language",
53
- "cache-control",
54
- "content-type",
55
- "user-agent",
56
- "x-api-key",
57
- "x-auth-token",
58
- "x-custom-header",
59
- "x-langflow-session",
60
- "x-mcp-client",
61
- "x-requested-with",
62
- }
63
-
64
-
65
- def validate_headers(headers: dict[str, str]) -> dict[str, str]:
66
- """Validate and sanitize HTTP headers according to RFC 7230.
67
-
68
- Args:
69
- headers: Dictionary of header name-value pairs
70
-
71
- Returns:
72
- Dictionary of validated and sanitized headers
73
-
74
- Raises:
75
- ValueError: If headers contain invalid names or values
76
- """
77
- if not headers:
78
- return {}
79
-
80
- sanitized_headers = {}
81
-
82
- for name, value in headers.items():
83
- if not isinstance(name, str) or not isinstance(value, str):
84
- logger.warning(f"Skipping non-string header: {name}={value}")
85
- continue
86
-
87
- # Validate header name according to RFC 7230
88
- if not HEADER_NAME_PATTERN.match(name):
89
- logger.warning(f"Invalid header name '{name}', skipping")
90
- continue
91
-
92
- # Normalize header name to lowercase (HTTP headers are case-insensitive)
93
- normalized_name = name.lower()
94
-
95
- # Optional: Check against whitelist of allowed headers
96
- if normalized_name not in ALLOWED_HEADERS:
97
- # For MCP, we'll be permissive and allow non-standard headers
98
- # but log a warning for security awareness
99
- logger.debug(f"Using non-standard header: {normalized_name}")
100
-
101
- # Check for potential header injection attempts BEFORE sanitizing
102
- if "\r" in value or "\n" in value:
103
- logger.warning(f"Potential header injection detected in '{name}', skipping")
104
- continue
105
-
106
- # Sanitize header value - remove control characters and newlines
107
- # RFC 7230: field-value = *( field-content / obs-fold )
108
- # We'll remove control characters (0x00-0x1F, 0x7F) except tab (0x09) and space (0x20)
109
- sanitized_value = re.sub(r"[\x00-\x08\x0A-\x1F\x7F]", "", value)
110
-
111
- # Remove leading/trailing whitespace
112
- sanitized_value = sanitized_value.strip()
113
-
114
- if not sanitized_value:
115
- logger.warning(f"Header '{name}' has empty value after sanitization, skipping")
116
- continue
117
-
118
- sanitized_headers[normalized_name] = sanitized_value
119
-
120
- return sanitized_headers
121
-
122
-
123
- def sanitize_mcp_name(name: str, max_length: int = 46) -> str:
124
- """Sanitize a name for MCP usage by removing emojis, diacritics, and special characters.
125
-
126
- Args:
127
- name: The original name to sanitize
128
- max_length: Maximum length for the sanitized name
129
-
130
- Returns:
131
- A sanitized name containing only letters, numbers, hyphens, and underscores
132
- """
133
- if not name or not name.strip():
134
- return ""
135
-
136
- # Remove emojis using regex pattern
137
- emoji_pattern = re.compile(
138
- "["
139
- "\U0001f600-\U0001f64f" # emoticons
140
- "\U0001f300-\U0001f5ff" # symbols & pictographs
141
- "\U0001f680-\U0001f6ff" # transport & map symbols
142
- "\U0001f1e0-\U0001f1ff" # flags (iOS)
143
- "\U00002500-\U00002bef" # chinese char
144
- "\U00002702-\U000027b0"
145
- "\U00002702-\U000027b0"
146
- "\U000024c2-\U0001f251"
147
- "\U0001f926-\U0001f937"
148
- "\U00010000-\U0010ffff"
149
- "\u2640-\u2642"
150
- "\u2600-\u2b55"
151
- "\u200d"
152
- "\u23cf"
153
- "\u23e9"
154
- "\u231a"
155
- "\ufe0f" # dingbats
156
- "\u3030"
157
- "]+",
158
- flags=re.UNICODE,
159
- )
160
-
161
- # Remove emojis
162
- name = emoji_pattern.sub("", name)
163
-
164
- # Normalize unicode characters to remove diacritics
165
- name = unicodedata.normalize("NFD", name)
166
- name = "".join(char for char in name if unicodedata.category(char) != "Mn")
167
-
168
- # Replace spaces and special characters with underscores
169
- name = re.sub(r"[^\w\s-]", "", name) # Keep only word chars, spaces, and hyphens
170
- name = re.sub(r"[-\s]+", "_", name) # Replace spaces and hyphens with underscores
171
- name = re.sub(r"_+", "_", name) # Collapse multiple underscores
172
-
173
- # Remove leading/trailing underscores
174
- name = name.strip("_")
175
-
176
- # Ensure it starts with a letter or underscore (not a number)
177
- if name and name[0].isdigit():
178
- name = f"_{name}"
179
-
180
- # Convert to lowercase
181
- name = name.lower()
182
-
183
- # Truncate to max length
184
- if len(name) > max_length:
185
- name = name[:max_length].rstrip("_")
186
-
187
- # If empty after sanitization, provide a default
188
- if not name:
189
- name = "unnamed"
190
-
191
- return name
192
-
193
-
194
- def create_tool_coroutine(tool_name: str, arg_schema: type[BaseModel], client) -> Callable[..., Awaitable]:
195
- async def tool_coroutine(*args, **kwargs):
196
- # Get field names from the model (preserving order)
197
- field_names = list(arg_schema.model_fields.keys())
198
- provided_args = {}
199
- # Map positional arguments to their corresponding field names
200
- for i, arg in enumerate(args):
201
- if i >= len(field_names):
202
- msg = "Too many positional arguments provided"
203
- raise ValueError(msg)
204
- provided_args[field_names[i]] = arg
205
- # Merge in keyword arguments
206
- provided_args.update(kwargs)
207
- # Validate input and fill defaults for missing optional fields
208
- try:
209
- validated = arg_schema.model_validate(provided_args)
210
- except Exception as e:
211
- msg = f"Invalid input: {e}"
212
- raise ValueError(msg) from e
213
-
214
- try:
215
- return await client.run_tool(tool_name, arguments=validated.model_dump())
216
- except Exception as e:
217
- await logger.aerror(f"Tool '{tool_name}' execution failed: {e}")
218
- # Re-raise with more context
219
- msg = f"Tool '{tool_name}' execution failed: {e}"
220
- raise ValueError(msg) from e
221
-
222
- return tool_coroutine
223
-
224
-
225
- def create_tool_func(tool_name: str, arg_schema: type[BaseModel], client) -> Callable[..., str]:
226
- def tool_func(*args, **kwargs):
227
- field_names = list(arg_schema.model_fields.keys())
228
- provided_args = {}
229
- for i, arg in enumerate(args):
230
- if i >= len(field_names):
231
- msg = "Too many positional arguments provided"
232
- raise ValueError(msg)
233
- provided_args[field_names[i]] = arg
234
- provided_args.update(kwargs)
235
- try:
236
- validated = arg_schema.model_validate(provided_args)
237
- except Exception as e:
238
- msg = f"Invalid input: {e}"
239
- raise ValueError(msg) from e
240
-
241
- try:
242
- loop = asyncio.get_event_loop()
243
- return loop.run_until_complete(client.run_tool(tool_name, arguments=validated.model_dump()))
244
- except Exception as e:
245
- logger.error(f"Tool '{tool_name}' execution failed: {e}")
246
- # Re-raise with more context
247
- msg = f"Tool '{tool_name}' execution failed: {e}"
248
- raise ValueError(msg) from e
249
-
250
- return tool_func
251
-
252
-
253
- def get_unique_name(base_name, max_length, existing_names):
254
- name = base_name[:max_length]
255
- if name not in existing_names:
256
- return name
257
- i = 1
258
- while True:
259
- suffix = f"_{i}"
260
- truncated_base = base_name[: max_length - len(suffix)]
261
- candidate = f"{truncated_base}{suffix}"
262
- if candidate not in existing_names:
263
- return candidate
264
- i += 1
265
-
266
-
267
- async def get_flow_snake_case(flow_name: str, user_id: str, session, *, is_action: bool | None = None) -> Flow | None:
268
- uuid_user_id = UUID(user_id) if isinstance(user_id, str) else user_id
269
- stmt = select(Flow).where(Flow.user_id == uuid_user_id).where(Flow.is_component == False) # noqa: E712
270
- flows = (await session.exec(stmt)).all()
271
-
272
- for flow in flows:
273
- if is_action and flow.action_name:
274
- this_flow_name = sanitize_mcp_name(flow.action_name)
275
- else:
276
- this_flow_name = sanitize_mcp_name(flow.name)
277
-
278
- if this_flow_name == flow_name:
279
- return flow
280
- return None
281
-
282
-
283
- def create_input_schema_from_json_schema(schema: dict[str, Any]) -> type[BaseModel]:
284
- """Dynamically build a Pydantic model from a JSON schema (with $defs).
285
-
286
- Non-required fields become Optional[...] with default=None.
287
- """
288
- if schema.get("type") != "object":
289
- msg = "Root schema must be type 'object'"
290
- raise ValueError(msg)
291
-
292
- defs: dict[str, dict[str, Any]] = schema.get("$defs", {})
293
- model_cache: dict[str, type[BaseModel]] = {}
294
-
295
- def resolve_ref(s: dict[str, Any] | None) -> dict[str, Any]:
296
- """Follow a $ref chain until you land on a real subschema."""
297
- if s is None:
298
- return {}
299
- while "$ref" in s:
300
- ref_name = s["$ref"].split("/")[-1]
301
- s = defs.get(ref_name)
302
- if s is None:
303
- logger.warning(f"Parsing input schema: Definition '{ref_name}' not found")
304
- return {"type": "string"}
305
- return s
306
-
307
- def parse_type(s: dict[str, Any] | None) -> Any:
308
- """Map a JSON Schema subschema to a Python type (possibly nested)."""
309
- if s is None:
310
- return None
311
- s = resolve_ref(s)
312
-
313
- if "anyOf" in s:
314
- # Handle common pattern for nullable types (anyOf with string and null)
315
- subtypes = [sub.get("type") for sub in s["anyOf"] if isinstance(sub, dict) and "type" in sub]
316
-
317
- # Check if this is a simple nullable type (e.g., str | None)
318
- if len(subtypes) == NULLABLE_TYPE_LENGTH and "null" in subtypes:
319
- # Get the non-null type
320
- non_null_type = next(t for t in subtypes if t != "null")
321
- # Map it to Python type
322
- if isinstance(non_null_type, str):
323
- return {
324
- "string": str,
325
- "integer": int,
326
- "number": float,
327
- "boolean": bool,
328
- "object": dict,
329
- "array": list,
330
- }.get(non_null_type, Any)
331
- return Any
332
-
333
- # For other anyOf cases, use the first non-null type
334
- subtypes = [parse_type(sub) for sub in s["anyOf"]]
335
- non_null_types = [t for t in subtypes if t is not None and t is not type(None)]
336
- if non_null_types:
337
- return non_null_types[0]
338
- return str
339
-
340
- t = s.get("type", "any") # Use string "any" as default instead of Any type
341
- if t == "array":
342
- item_schema = s.get("items", {})
343
- schema_type: Any = parse_type(item_schema)
344
- return list[schema_type]
345
-
346
- if t == "object":
347
- # inline object not in $defs ⇒ anonymous nested model
348
- return _build_model(f"AnonModel{len(model_cache)}", s)
349
-
350
- # primitive fallback
351
- return {
352
- "string": str,
353
- "integer": int,
354
- "number": float,
355
- "boolean": bool,
356
- "object": dict,
357
- "array": list,
358
- }.get(t, Any)
359
-
360
- def _build_model(name: str, subschema: dict[str, Any]) -> type[BaseModel]:
361
- """Create (or fetch) a BaseModel subclass for the given object schema."""
362
- # If this came via a named $ref, use that name
363
- if "$ref" in subschema:
364
- refname = subschema["$ref"].split("/")[-1]
365
- if refname in model_cache:
366
- return model_cache[refname]
367
- target = defs.get(refname)
368
- if not target:
369
- msg = f"Definition '{refname}' not found"
370
- raise ValueError(msg)
371
- cls = _build_model(refname, target)
372
- model_cache[refname] = cls
373
- return cls
374
-
375
- # Named anonymous or inline: avoid clashes by name
376
- if name in model_cache:
377
- return model_cache[name]
378
-
379
- props = subschema.get("properties", {})
380
- reqs = set(subschema.get("required", []))
381
- fields: dict[str, Any] = {}
382
-
383
- for prop_name, prop_schema in props.items():
384
- py_type = parse_type(prop_schema)
385
- is_required = prop_name in reqs
386
- if not is_required:
387
- py_type = py_type | None
388
- default = prop_schema.get("default", None)
389
- else:
390
- default = ... # required by Pydantic
391
-
392
- fields[prop_name] = (py_type, Field(default, description=prop_schema.get("description")))
393
-
394
- model_cls = create_model(name, **fields)
395
- model_cache[name] = model_cls
396
- return model_cls
397
-
398
- # build the top - level "InputSchema" from the root properties
399
- top_props = schema.get("properties", {})
400
- top_reqs = set(schema.get("required", []))
401
- top_fields: dict[str, Any] = {}
402
-
403
- for fname, fdef in top_props.items():
404
- py_type = parse_type(fdef)
405
- if fname not in top_reqs:
406
- py_type = py_type | None
407
- default = fdef.get("default", None)
408
- else:
409
- default = ...
410
- top_fields[fname] = (py_type, Field(default, description=fdef.get("description")))
411
-
412
- return create_model("InputSchema", **top_fields)
413
-
414
-
415
- def _is_valid_key_value_item(item: Any) -> bool:
416
- """Check if an item is a valid key-value dictionary."""
417
- return isinstance(item, dict) and "key" in item and "value" in item
418
-
419
-
420
- def _process_headers(headers: Any) -> dict:
421
- """Process the headers input into a valid dictionary.
422
-
423
- Args:
424
- headers: The headers to process, can be dict, str, or list
425
- Returns:
426
- Processed and validated dictionary
427
- """
428
- if headers is None:
429
- return {}
430
- if isinstance(headers, dict):
431
- return validate_headers(headers)
432
- if isinstance(headers, list):
433
- processed_headers = {}
434
- try:
435
- for item in headers:
436
- if not _is_valid_key_value_item(item):
437
- continue
438
- key = item["key"]
439
- value = item["value"]
440
- processed_headers[key] = value
441
- except (KeyError, TypeError, ValueError):
442
- return {} # Return empty dictionary instead of None
443
- return validate_headers(processed_headers)
444
- return {}
445
-
446
-
447
- def _validate_node_installation(command: str) -> str:
448
- """Validate the npx command."""
449
- if "npx" in command and not shutil.which("node"):
450
- msg = "Node.js is not installed. Please install Node.js to use npx commands."
451
- raise ValueError(msg)
452
- return command
453
-
454
-
455
- async def _validate_connection_params(mode: str, command: str | None = None, url: str | None = None) -> None:
456
- """Validate connection parameters based on mode."""
457
- if mode not in ["Stdio", "SSE"]:
458
- msg = f"Invalid mode: {mode}. Must be either 'Stdio' or 'SSE'"
459
- raise ValueError(msg)
460
-
461
- if mode == "Stdio" and not command:
462
- msg = "Command is required for Stdio mode"
463
- raise ValueError(msg)
464
- if mode == "Stdio" and command:
465
- _validate_node_installation(command)
466
- if mode == "SSE" and not url:
467
- msg = "URL is required for SSE mode"
468
- raise ValueError(msg)
469
-
470
-
471
- class MCPSessionManager:
472
- """Manages persistent MCP sessions with proper context manager lifecycle.
473
-
474
- Fixed version that addresses the memory leak issue by:
475
- 1. Session reuse based on server identity rather than unique context IDs
476
- 2. Maximum session limits per server to prevent resource exhaustion
477
- 3. Idle timeout for automatic session cleanup
478
- 4. Periodic cleanup of stale sessions
479
- """
480
-
481
- def __init__(self):
482
- # Structure: server_key -> {"sessions": {session_id: session_info}, "last_cleanup": timestamp}
483
- self.sessions_by_server = {}
484
- self._background_tasks = set() # Keep references to background tasks
485
- # Backwards-compatibility maps: which context_id uses which (server_key, session_id)
486
- self._context_to_session: dict[str, tuple[str, str]] = {}
487
- # Reference count for each active (server_key, session_id)
488
- self._session_refcount: dict[tuple[str, str], int] = {}
489
- self._cleanup_task = None
490
- self._start_cleanup_task()
491
-
492
- def _start_cleanup_task(self):
493
- """Start the periodic cleanup task."""
494
- if self._cleanup_task is None or self._cleanup_task.done():
495
- self._cleanup_task = asyncio.create_task(self._periodic_cleanup())
496
- self._background_tasks.add(self._cleanup_task)
497
- self._cleanup_task.add_done_callback(self._background_tasks.discard)
498
-
499
- async def _periodic_cleanup(self):
500
- """Periodically clean up idle sessions."""
501
- while True:
502
- try:
503
- await asyncio.sleep(SESSION_CLEANUP_INTERVAL)
504
- await self._cleanup_idle_sessions()
505
- except asyncio.CancelledError:
506
- break
507
- except (RuntimeError, KeyError, ClosedResourceError, ValueError, asyncio.TimeoutError) as e:
508
- # Handle common recoverable errors without stopping the cleanup loop
509
- await logger.awarning(f"Error in periodic cleanup: {e}")
510
-
511
- async def _cleanup_idle_sessions(self):
512
- """Clean up sessions that have been idle for too long."""
513
- current_time = asyncio.get_event_loop().time()
514
- servers_to_remove = []
515
-
516
- for server_key, server_data in self.sessions_by_server.items():
517
- sessions = server_data.get("sessions", {})
518
- sessions_to_remove = []
519
-
520
- for session_id, session_info in sessions.items():
521
- if current_time - session_info["last_used"] > SESSION_IDLE_TIMEOUT:
522
- sessions_to_remove.append(session_id)
523
-
524
- # Clean up idle sessions
525
- for session_id in sessions_to_remove:
526
- await logger.ainfo(f"Cleaning up idle session {session_id} for server {server_key}")
527
- await self._cleanup_session_by_id(server_key, session_id)
528
-
529
- # Remove server entry if no sessions left
530
- if not sessions:
531
- servers_to_remove.append(server_key)
532
-
533
- # Clean up empty server entries
534
- for server_key in servers_to_remove:
535
- del self.sessions_by_server[server_key]
536
-
537
- def _get_server_key(self, connection_params, transport_type: str) -> str:
538
- """Generate a consistent server key based on connection parameters."""
539
- if transport_type == "stdio":
540
- if hasattr(connection_params, "command"):
541
- # Include command, args, and environment for uniqueness
542
- command_str = f"{connection_params.command} {' '.join(connection_params.args or [])}"
543
- env_str = str(sorted((connection_params.env or {}).items()))
544
- key_input = f"{command_str}|{env_str}"
545
- return f"stdio_{hash(key_input)}"
546
- elif transport_type == "sse" and (isinstance(connection_params, dict) and "url" in connection_params):
547
- # Include URL and headers for uniqueness
548
- url = connection_params["url"]
549
- headers = str(sorted((connection_params.get("headers", {})).items()))
550
- key_input = f"{url}|{headers}"
551
- return f"sse_{hash(key_input)}"
552
-
553
- # Fallback to a generic key
554
- # TODO: add option for streamable HTTP in future.
555
- return f"{transport_type}_{hash(str(connection_params))}"
556
-
557
- async def _validate_session_connectivity(self, session) -> bool:
558
- """Validate that the session is actually usable by testing a simple operation."""
559
- try:
560
- # Try to list tools as a connectivity test (this is a lightweight operation)
561
- # Use a shorter timeout for the connectivity test to fail fast
562
- response = await asyncio.wait_for(session.list_tools(), timeout=3.0)
563
- except (asyncio.TimeoutError, ConnectionError, OSError, ValueError) as e:
564
- await logger.adebug(f"Session connectivity test failed (standard error): {e}")
565
- return False
566
- except Exception as e:
567
- # Handle MCP-specific errors that might not be in the standard list
568
- error_str = str(e)
569
- if (
570
- "ClosedResourceError" in str(type(e))
571
- or "Connection closed" in error_str
572
- or "Connection lost" in error_str
573
- or "Connection failed" in error_str
574
- or "Transport closed" in error_str
575
- or "Stream closed" in error_str
576
- ):
577
- await logger.adebug(f"Session connectivity test failed (MCP connection error): {e}")
578
- return False
579
- # Re-raise unexpected errors
580
- await logger.awarning(f"Unexpected error in connectivity test: {e}")
581
- raise
582
- else:
583
- # Validate that we got a meaningful response
584
- if response is None:
585
- await logger.adebug("Session connectivity test failed: received None response")
586
- return False
587
- try:
588
- # Check if we can access the tools list (even if empty)
589
- tools = getattr(response, "tools", None)
590
- if tools is None:
591
- await logger.adebug("Session connectivity test failed: no tools attribute in response")
592
- return False
593
- except (AttributeError, TypeError) as e:
594
- await logger.adebug(f"Session connectivity test failed while validating response: {e}")
595
- return False
596
- else:
597
- await logger.adebug(f"Session connectivity test passed: found {len(tools)} tools")
598
- return True
599
-
600
- async def get_session(self, context_id: str, connection_params, transport_type: str):
601
- """Get or create a session with improved reuse strategy.
602
-
603
- The key insight is that we should reuse sessions based on the server
604
- identity (command + args for stdio, URL for SSE) rather than the context_id.
605
- This prevents creating a new subprocess for each unique context.
606
- """
607
- server_key = self._get_server_key(connection_params, transport_type)
608
-
609
- # Ensure server entry exists
610
- if server_key not in self.sessions_by_server:
611
- self.sessions_by_server[server_key] = {"sessions": {}, "last_cleanup": asyncio.get_event_loop().time()}
612
-
613
- server_data = self.sessions_by_server[server_key]
614
- sessions = server_data["sessions"]
615
-
616
- # Try to find a healthy existing session
617
- for session_id, session_info in sessions.items():
618
- session = session_info["session"]
619
- task = session_info["task"]
620
-
621
- # Check if session is still alive
622
- if not task.done():
623
- # Update last used time
624
- session_info["last_used"] = asyncio.get_event_loop().time()
625
-
626
- # Quick health check
627
- if await self._validate_session_connectivity(session):
628
- await logger.adebug(f"Reusing existing session {session_id} for server {server_key}")
629
- # record mapping & bump ref-count for backwards compatibility
630
- self._context_to_session[context_id] = (server_key, session_id)
631
- self._session_refcount[(server_key, session_id)] = (
632
- self._session_refcount.get((server_key, session_id), 0) + 1
633
- )
634
- return session
635
- await logger.ainfo(f"Session {session_id} for server {server_key} failed health check, cleaning up")
636
- await self._cleanup_session_by_id(server_key, session_id)
637
- else:
638
- # Task is done, clean up
639
- await logger.ainfo(f"Session {session_id} for server {server_key} task is done, cleaning up")
640
- await self._cleanup_session_by_id(server_key, session_id)
641
-
642
- # Check if we've reached the maximum number of sessions for this server
643
- if len(sessions) >= MAX_SESSIONS_PER_SERVER:
644
- # Remove the oldest session
645
- oldest_session_id = min(sessions.keys(), key=lambda x: sessions[x]["last_used"])
646
- await logger.ainfo(
647
- f"Maximum sessions reached for server {server_key}, removing oldest session {oldest_session_id}"
648
- )
649
- await self._cleanup_session_by_id(server_key, oldest_session_id)
650
-
651
- # Create new session
652
- session_id = f"{server_key}_{len(sessions)}"
653
- await logger.ainfo(f"Creating new session {session_id} for server {server_key}")
654
-
655
- if transport_type == "stdio":
656
- session, task = await self._create_stdio_session(session_id, connection_params)
657
- elif transport_type == "sse":
658
- session, task = await self._create_sse_session(session_id, connection_params)
659
- else:
660
- msg = f"Unknown transport type: {transport_type}"
661
- raise ValueError(msg)
662
-
663
- # Store session info
664
- sessions[session_id] = {
665
- "session": session,
666
- "task": task,
667
- "type": transport_type,
668
- "last_used": asyncio.get_event_loop().time(),
669
- }
670
-
671
- # register mapping & initial ref-count for the new session
672
- self._context_to_session[context_id] = (server_key, session_id)
673
- self._session_refcount[(server_key, session_id)] = 1
674
-
675
- return session
676
-
677
- async def _create_stdio_session(self, session_id: str, connection_params):
678
- """Create a new stdio session as a background task to avoid context issues."""
679
- import asyncio
680
-
681
- from mcp.client.stdio import stdio_client
682
-
683
- # Create a future to get the session
684
- session_future: asyncio.Future[ClientSession] = asyncio.Future()
685
-
686
- async def session_task():
687
- """Background task that keeps the session alive."""
688
- try:
689
- async with stdio_client(connection_params) as (read, write):
690
- session = ClientSession(read, write)
691
- async with session:
692
- await session.initialize()
693
- # Signal that session is ready
694
- session_future.set_result(session)
695
-
696
- # Keep the session alive until cancelled
697
- import anyio
698
-
699
- event = anyio.Event()
700
- try:
701
- await event.wait()
702
- except asyncio.CancelledError:
703
- await logger.ainfo(f"Session {session_id} is shutting down")
704
- except Exception as e: # noqa: BLE001
705
- if not session_future.done():
706
- session_future.set_exception(e)
707
-
708
- # Start the background task
709
- task = asyncio.create_task(session_task())
710
- self._background_tasks.add(task)
711
- task.add_done_callback(self._background_tasks.discard)
712
-
713
- # Wait for session to be ready
714
- try:
715
- session = await asyncio.wait_for(session_future, timeout=10.0)
716
- except asyncio.TimeoutError as timeout_err:
717
- # Clean up the failed task
718
- if not task.done():
719
- task.cancel()
720
- import contextlib
721
-
722
- with contextlib.suppress(asyncio.CancelledError):
723
- await task
724
- self._background_tasks.discard(task)
725
- msg = f"Timeout waiting for STDIO session {session_id} to initialize"
726
- await logger.aerror(msg)
727
- raise ValueError(msg) from timeout_err
728
-
729
- return session, task
730
-
731
- async def _create_sse_session(self, session_id: str, connection_params):
732
- """Create a new SSE session as a background task to avoid context issues."""
733
- import asyncio
734
-
735
- from mcp.client.sse import sse_client
736
-
737
- # Create a future to get the session
738
- session_future: asyncio.Future[ClientSession] = asyncio.Future()
739
-
740
- async def session_task():
741
- """Background task that keeps the session alive."""
742
- try:
743
- async with sse_client(
744
- connection_params["url"],
745
- connection_params["headers"],
746
- connection_params["timeout_seconds"],
747
- connection_params["sse_read_timeout_seconds"],
748
- ) as (read, write):
749
- session = ClientSession(read, write)
750
- async with session:
751
- await session.initialize()
752
- # Signal that session is ready
753
- session_future.set_result(session)
754
-
755
- # Keep the session alive until cancelled
756
- import anyio
757
-
758
- event = anyio.Event()
759
- try:
760
- await event.wait()
761
- except asyncio.CancelledError:
762
- await logger.ainfo(f"Session {session_id} is shutting down")
763
- except Exception as e: # noqa: BLE001
764
- if not session_future.done():
765
- session_future.set_exception(e)
766
-
767
- # Start the background task
768
- task = asyncio.create_task(session_task())
769
- self._background_tasks.add(task)
770
- task.add_done_callback(self._background_tasks.discard)
771
-
772
- # Wait for session to be ready
773
- try:
774
- session = await asyncio.wait_for(session_future, timeout=10.0)
775
- except asyncio.TimeoutError as timeout_err:
776
- # Clean up the failed task
777
- if not task.done():
778
- task.cancel()
779
- import contextlib
780
-
781
- with contextlib.suppress(asyncio.CancelledError):
782
- await task
783
- self._background_tasks.discard(task)
784
- msg = f"Timeout waiting for SSE session {session_id} to initialize"
785
- await logger.aerror(msg)
786
- raise ValueError(msg) from timeout_err
787
-
788
- return session, task
789
-
790
- async def _cleanup_session_by_id(self, server_key: str, session_id: str):
791
- """Clean up a specific session by server key and session ID."""
792
- if server_key not in self.sessions_by_server:
793
- return
794
-
795
- server_data = self.sessions_by_server[server_key]
796
- # Handle both old and new session structure
797
- if isinstance(server_data, dict) and "sessions" in server_data:
798
- sessions = server_data["sessions"]
799
- else:
800
- # Handle old structure where sessions were stored directly
801
- sessions = server_data
802
-
803
- if session_id not in sessions:
804
- return
805
-
806
- session_info = sessions[session_id]
807
- try:
808
- # First try to properly close the session if it exists
809
- if "session" in session_info:
810
- session = session_info["session"]
811
-
812
- # Try async close first (aclose method)
813
- if hasattr(session, "aclose"):
814
- try:
815
- await session.aclose()
816
- await logger.adebug("Successfully closed session %s using aclose()", session_id)
817
- except Exception as e: # noqa: BLE001
818
- await logger.adebug("Error closing session %s with aclose(): %s", session_id, e)
819
-
820
- # If no aclose, try regular close method
821
- elif hasattr(session, "close"):
822
- try:
823
- # Check if close() is awaitable using inspection
824
- if inspect.iscoroutinefunction(session.close):
825
- # It's an async method
826
- await session.close()
827
- await logger.adebug("Successfully closed session %s using async close()", session_id)
828
- else:
829
- # Try calling it and check if result is awaitable
830
- close_result = session.close()
831
- if inspect.isawaitable(close_result):
832
- await close_result
833
- await logger.adebug(
834
- "Successfully closed session %s using awaitable close()", session_id
835
- )
836
- else:
837
- # It's a synchronous close
838
- await logger.adebug("Successfully closed session %s using sync close()", session_id)
839
- except Exception as e: # noqa: BLE001
840
- await logger.adebug("Error closing session %s with close(): %s", session_id, e)
841
-
842
- # Cancel the background task which will properly close the session
843
- if "task" in session_info:
844
- task = session_info["task"]
845
- if not task.done():
846
- task.cancel()
847
- try:
848
- await task
849
- except asyncio.CancelledError:
850
- await logger.ainfo(f"Cancelled task for session {session_id}")
851
- except Exception as e: # noqa: BLE001
852
- await logger.awarning(f"Error cleaning up session {session_id}: {e}")
853
- finally:
854
- # Remove from sessions dict
855
- del sessions[session_id]
856
-
857
- async def cleanup_all(self):
858
- """Clean up all sessions."""
859
- # Cancel periodic cleanup task
860
- if self._cleanup_task and not self._cleanup_task.done():
861
- self._cleanup_task.cancel()
862
- with contextlib.suppress(asyncio.CancelledError):
863
- await self._cleanup_task
864
-
865
- # Clean up all sessions
866
- for server_key in list(self.sessions_by_server.keys()):
867
- server_data = self.sessions_by_server[server_key]
868
- # Handle both old and new session structure
869
- if isinstance(server_data, dict) and "sessions" in server_data:
870
- sessions = server_data["sessions"]
871
- else:
872
- # Handle old structure where sessions were stored directly
873
- sessions = server_data
874
-
875
- for session_id in list(sessions.keys()):
876
- await self._cleanup_session_by_id(server_key, session_id)
877
-
878
- # Clear the sessions_by_server structure completely
879
- self.sessions_by_server.clear()
880
-
881
- # Clear compatibility maps
882
- self._context_to_session.clear()
883
- self._session_refcount.clear()
884
-
885
- # Clear all background tasks
886
- for task in list(self._background_tasks):
887
- if not task.done():
888
- task.cancel()
889
- with contextlib.suppress(asyncio.CancelledError):
890
- await task
891
-
892
- # Give a bit more time for subprocess transports to clean up
893
- # This helps prevent the BaseSubprocessTransport.__del__ warnings
894
- await asyncio.sleep(0.5)
895
-
896
- async def _cleanup_session(self, context_id: str):
897
- """Backward-compat cleanup by context_id.
898
-
899
- Decrements the ref-count for the session used by *context_id* and only
900
- tears the session down when the last context that references it goes
901
- away.
902
- """
903
- mapping = self._context_to_session.get(context_id)
904
- if not mapping:
905
- await logger.adebug(f"No session mapping found for context_id {context_id}")
906
- return
907
-
908
- server_key, session_id = mapping
909
- ref_key = (server_key, session_id)
910
- remaining = self._session_refcount.get(ref_key, 1) - 1
911
-
912
- if remaining <= 0:
913
- await self._cleanup_session_by_id(server_key, session_id)
914
- self._session_refcount.pop(ref_key, None)
915
- else:
916
- self._session_refcount[ref_key] = remaining
917
-
918
- # Remove the mapping for this context
919
- self._context_to_session.pop(context_id, None)
920
-
921
-
922
- class MCPStdioClient:
923
- def __init__(self, component_cache=None):
924
- self.session: ClientSession | None = None
925
- self._connection_params = None
926
- self._connected = False
927
- self._session_context: str | None = None
928
- self._component_cache = component_cache
929
-
930
- async def _connect_to_server(self, command_str: str, env: dict[str, str] | None = None) -> list[StructuredTool]:
931
- """Connect to MCP server using stdio transport (SDK style)."""
932
- from mcp import StdioServerParameters
933
-
934
- command = command_str.split(" ")
935
- env_data: dict[str, str] = {"DEBUG": "true", "PATH": os.environ["PATH"], **(env or {})}
936
-
937
- if platform.system() == "Windows":
938
- server_params = StdioServerParameters(
939
- command="cmd",
940
- args=[
941
- "/c",
942
- f"{command[0]} {' '.join(command[1:])} || echo Command failed with exit code %errorlevel% 1>&2",
943
- ],
944
- env=env_data,
945
- )
946
- else:
947
- server_params = StdioServerParameters(
948
- command="bash",
949
- args=["-c", f"exec {command_str} || echo 'Command failed with exit code $?' >&2"],
950
- env=env_data,
951
- )
952
-
953
- # Store connection parameters for later use in run_tool
954
- self._connection_params = server_params
955
-
956
- # If no session context is set, create a default one
957
- if not self._session_context:
958
- # Generate a fallback context based on connection parameters
959
- import uuid
960
-
961
- param_hash = uuid.uuid4().hex[:8]
962
- self._session_context = f"default_{param_hash}"
963
-
964
- # Get or create a persistent session
965
- session = await self._get_or_create_session()
966
- response = await session.list_tools()
967
- self._connected = True
968
- return response.tools
969
-
970
- async def connect_to_server(self, command_str: str, env: dict[str, str] | None = None) -> list[StructuredTool]:
971
- """Connect to MCP server using stdio transport (SDK style)."""
972
- return await asyncio.wait_for(
973
- self._connect_to_server(command_str, env), timeout=get_settings_service().settings.mcp_server_timeout
974
- )
975
-
976
- def set_session_context(self, context_id: str):
977
- """Set the session context (e.g., flow_id + user_id + session_id)."""
978
- self._session_context = context_id
979
-
980
- def _get_session_manager(self) -> MCPSessionManager:
981
- """Get or create session manager from component cache."""
982
- if not self._component_cache:
983
- # Fallback to instance-level session manager if no cache
984
- if not hasattr(self, "_session_manager"):
985
- self._session_manager = MCPSessionManager()
986
- return self._session_manager
987
-
988
- from langflow.services.cache.utils import CacheMiss
989
-
990
- session_manager = self._component_cache.get("mcp_session_manager")
991
- if isinstance(session_manager, CacheMiss):
992
- session_manager = MCPSessionManager()
993
- self._component_cache.set("mcp_session_manager", session_manager)
994
- return session_manager
995
-
996
- async def _get_or_create_session(self) -> ClientSession:
997
- """Get or create a persistent session for the current context."""
998
- if not self._session_context or not self._connection_params:
999
- msg = "Session context and connection params must be set"
1000
- raise ValueError(msg)
1001
-
1002
- # Use cached session manager to get/create persistent session
1003
- session_manager = self._get_session_manager()
1004
- return await session_manager.get_session(self._session_context, self._connection_params, "stdio")
1005
-
1006
- async def run_tool(self, tool_name: str, arguments: dict[str, Any]) -> Any:
1007
- """Run a tool with the given arguments using context-specific session.
1008
-
1009
- Args:
1010
- tool_name: Name of the tool to run
1011
- arguments: Dictionary of arguments to pass to the tool
1012
-
1013
- Returns:
1014
- The result of the tool execution
1015
-
1016
- Raises:
1017
- ValueError: If session is not initialized or tool execution fails
1018
- """
1019
- if not self._connected or not self._connection_params:
1020
- msg = "Session not initialized or disconnected. Call connect_to_server first."
1021
- raise ValueError(msg)
1022
-
1023
- # If no session context is set, create a default one
1024
- if not self._session_context:
1025
- # Generate a fallback context based on connection parameters
1026
- import uuid
1027
-
1028
- param_hash = uuid.uuid4().hex[:8]
1029
- self._session_context = f"default_{param_hash}"
1030
-
1031
- max_retries = 2
1032
- last_error_type = None
1033
-
1034
- for attempt in range(max_retries):
1035
- try:
1036
- await logger.adebug(f"Attempting to run tool '{tool_name}' (attempt {attempt + 1}/{max_retries})")
1037
- # Get or create persistent session
1038
- session = await self._get_or_create_session()
1039
-
1040
- result = await asyncio.wait_for(
1041
- session.call_tool(tool_name, arguments=arguments),
1042
- timeout=30.0, # 30 second timeout
1043
- )
1044
- except Exception as e:
1045
- current_error_type = type(e).__name__
1046
- await logger.awarning(f"Tool '{tool_name}' failed on attempt {attempt + 1}: {current_error_type} - {e}")
1047
-
1048
- # Import specific MCP error types for detection
1049
- try:
1050
- is_closed_resource_error = isinstance(e, ClosedResourceError)
1051
- is_mcp_connection_error = isinstance(e, McpError) and "Connection closed" in str(e)
1052
- except ImportError:
1053
- is_closed_resource_error = "ClosedResourceError" in str(type(e))
1054
- is_mcp_connection_error = "Connection closed" in str(e)
1055
-
1056
- # Detect timeout errors
1057
- is_timeout_error = isinstance(e, asyncio.TimeoutError | TimeoutError)
1058
-
1059
- # If we're getting the same error type repeatedly, don't retry
1060
- if last_error_type == current_error_type and attempt > 0:
1061
- await logger.aerror(f"Repeated {current_error_type} error for tool '{tool_name}', not retrying")
1062
- break
1063
-
1064
- last_error_type = current_error_type
1065
-
1066
- # If it's a connection error (ClosedResourceError or MCP connection closed) and we have retries left
1067
- if (is_closed_resource_error or is_mcp_connection_error) and attempt < max_retries - 1:
1068
- await logger.awarning(
1069
- f"MCP session connection issue for tool '{tool_name}', retrying with fresh session..."
1070
- )
1071
- # Clean up the dead session
1072
- if self._session_context:
1073
- session_manager = self._get_session_manager()
1074
- await session_manager._cleanup_session(self._session_context)
1075
- # Add a small delay before retry
1076
- await asyncio.sleep(0.5)
1077
- continue
1078
-
1079
- # If it's a timeout error and we have retries left, try once more
1080
- if is_timeout_error and attempt < max_retries - 1:
1081
- await logger.awarning(f"Tool '{tool_name}' timed out, retrying...")
1082
- # Don't clean up session for timeouts, might just be a slow response
1083
- await asyncio.sleep(1.0)
1084
- continue
1085
-
1086
- # For other errors or no retries left, handle as before
1087
- if (
1088
- isinstance(e, ConnectionError | TimeoutError | OSError | ValueError)
1089
- or is_closed_resource_error
1090
- or is_mcp_connection_error
1091
- or is_timeout_error
1092
- ):
1093
- msg = f"Failed to run tool '{tool_name}' after {attempt + 1} attempts: {e}"
1094
- await logger.aerror(msg)
1095
- # Clean up failed session from cache
1096
- if self._session_context and self._component_cache:
1097
- cache_key = f"mcp_session_stdio_{self._session_context}"
1098
- self._component_cache.delete(cache_key)
1099
- self._connected = False
1100
- raise ValueError(msg) from e
1101
- # Re-raise unexpected errors
1102
- raise
1103
- else:
1104
- await logger.adebug(f"Tool '{tool_name}' completed successfully")
1105
- return result
1106
-
1107
- # This should never be reached due to the exception handling above
1108
- msg = f"Failed to run tool '{tool_name}': Maximum retries exceeded with repeated {last_error_type} errors"
1109
- await logger.aerror(msg)
1110
- raise ValueError(msg)
1111
-
1112
- async def disconnect(self):
1113
- """Properly close the connection and clean up resources."""
1114
- # For stdio transport, there is no remote session to terminate explicitly
1115
- # The session cleanup happens when the background task is cancelled
1116
-
1117
- # Clean up local session using the session manager
1118
- if self._session_context:
1119
- session_manager = self._get_session_manager()
1120
- await session_manager._cleanup_session(self._session_context)
1121
-
1122
- # Reset local state
1123
- self.session = None
1124
- self._connection_params = None
1125
- self._connected = False
1126
- self._session_context = None
1127
-
1128
- async def __aenter__(self):
1129
- return self
1130
-
1131
- async def __aexit__(self, exc_type, exc_val, exc_tb):
1132
- await self.disconnect()
1133
-
1134
-
1135
- class MCPSseClient:
1136
- def __init__(self, component_cache=None):
1137
- self.session: ClientSession | None = None
1138
- self._connection_params = None
1139
- self._connected = False
1140
- self._session_context: str | None = None
1141
- self._component_cache = component_cache
1142
-
1143
- def _get_session_manager(self) -> MCPSessionManager:
1144
- """Get or create session manager from component cache."""
1145
- if not self._component_cache:
1146
- # Fallback to instance-level session manager if no cache
1147
- if not hasattr(self, "_session_manager"):
1148
- self._session_manager = MCPSessionManager()
1149
- return self._session_manager
1150
-
1151
- from langflow.services.cache.utils import CacheMiss
1152
-
1153
- session_manager = self._component_cache.get("mcp_session_manager")
1154
- if isinstance(session_manager, CacheMiss):
1155
- session_manager = MCPSessionManager()
1156
- self._component_cache.set("mcp_session_manager", session_manager)
1157
- return session_manager
1158
-
1159
- async def validate_url(self, url: str | None, headers: dict[str, str] | None = None) -> tuple[bool, str]:
1160
- """Validate the SSE URL before attempting connection."""
1161
- try:
1162
- parsed = urlparse(url)
1163
- if not parsed.scheme or not parsed.netloc:
1164
- return False, "Invalid URL format. Must include scheme (http/https) and host."
1165
-
1166
- async with httpx.AsyncClient() as client:
1167
- try:
1168
- # For SSE endpoints, try a GET request with short timeout
1169
- # Many SSE servers don't support HEAD requests and return 404
1170
- response = await client.get(
1171
- url, timeout=2.0, headers={"Accept": "text/event-stream", **(headers or {})}
1172
- )
1173
-
1174
- # For SSE, we expect the server to either:
1175
- # 1. Start streaming (200)
1176
- # 2. Return 404 if HEAD/GET without proper SSE handshake is not supported
1177
- # 3. Return other status codes that we should handle gracefully
1178
-
1179
- # Don't fail on 404 since many SSE endpoints return this for non-SSE requests
1180
- if response.status_code == HTTP_NOT_FOUND:
1181
- # This is likely an SSE endpoint that doesn't support regular GET
1182
- # Let the actual SSE connection attempt handle this
1183
- return True, ""
1184
-
1185
- # Fail on client errors except 404, but allow server errors and redirects
1186
- if (
1187
- HTTP_BAD_REQUEST <= response.status_code < HTTP_INTERNAL_SERVER_ERROR
1188
- and response.status_code != HTTP_NOT_FOUND
1189
- ):
1190
- return False, f"Server returned client error status: {response.status_code}"
1191
-
1192
- except httpx.TimeoutException:
1193
- # Timeout on a short request might indicate the server is trying to stream
1194
- # This is actually expected behavior for SSE endpoints
1195
- return True, ""
1196
- except httpx.NetworkError:
1197
- return False, "Network error. Could not reach the server."
1198
- else:
1199
- return True, ""
1200
-
1201
- except (httpx.HTTPError, ValueError, OSError) as e:
1202
- return False, f"URL validation error: {e!s}"
1203
-
1204
- async def pre_check_redirect(self, url: str | None, headers: dict[str, str] | None = None) -> str | None:
1205
- """Check for redirects and return the final URL."""
1206
- if url is None:
1207
- return url
1208
- try:
1209
- async with httpx.AsyncClient(follow_redirects=False) as client:
1210
- # Use GET with SSE headers instead of HEAD since many SSE servers don't support HEAD
1211
- response = await client.get(
1212
- url, timeout=2.0, headers={"Accept": "text/event-stream", **(headers or {})}
1213
- )
1214
- if response.status_code == httpx.codes.TEMPORARY_REDIRECT:
1215
- return response.headers.get("Location", url)
1216
- # Don't treat 404 as an error here - let the main connection handle it
1217
- except (httpx.RequestError, httpx.HTTPError) as e:
1218
- await logger.awarning(f"Error checking redirects: {e}")
1219
- return url
1220
-
1221
- async def _connect_to_server(
1222
- self,
1223
- url: str | None,
1224
- headers: dict[str, str] | None = None,
1225
- timeout_seconds: int = 30,
1226
- sse_read_timeout_seconds: int = 30,
1227
- ) -> list[StructuredTool]:
1228
- """Connect to MCP server using SSE transport (SDK style)."""
1229
- # Validate and sanitize headers early
1230
- validated_headers = _process_headers(headers)
1231
-
1232
- if url is None:
1233
- msg = "URL is required for SSE mode"
1234
- raise ValueError(msg)
1235
- is_valid, error_msg = await self.validate_url(url, validated_headers)
1236
- if not is_valid:
1237
- msg = f"Invalid SSE URL ({url}): {error_msg}"
1238
- raise ValueError(msg)
1239
-
1240
- url = await self.pre_check_redirect(url, validated_headers)
1241
-
1242
- # Store connection parameters for later use in run_tool
1243
- self._connection_params = {
1244
- "url": url,
1245
- "headers": validated_headers,
1246
- "timeout_seconds": timeout_seconds,
1247
- "sse_read_timeout_seconds": sse_read_timeout_seconds,
1248
- }
1249
-
1250
- # If no session context is set, create a default one
1251
- if not self._session_context:
1252
- # Generate a fallback context based on connection parameters
1253
- import uuid
1254
-
1255
- param_hash = uuid.uuid4().hex[:8]
1256
- self._session_context = f"default_sse_{param_hash}"
1257
-
1258
- # Get or create a persistent session
1259
- session = await self._get_or_create_session()
1260
- response = await session.list_tools()
1261
- self._connected = True
1262
- return response.tools
1263
-
1264
- async def connect_to_server(self, url: str, headers: dict[str, str] | None = None) -> list[StructuredTool]:
1265
- """Connect to MCP server using SSE transport (SDK style)."""
1266
- return await asyncio.wait_for(
1267
- self._connect_to_server(url, headers), timeout=get_settings_service().settings.mcp_server_timeout
1268
- )
1269
-
1270
- def set_session_context(self, context_id: str):
1271
- """Set the session context (e.g., flow_id + user_id + session_id)."""
1272
- self._session_context = context_id
1273
-
1274
- async def _get_or_create_session(self) -> ClientSession:
1275
- """Get or create a persistent session for the current context."""
1276
- if not self._session_context or not self._connection_params:
1277
- msg = "Session context and params must be set"
1278
- raise ValueError(msg)
1279
-
1280
- # Use cached session manager to get/create persistent session
1281
- session_manager = self._get_session_manager()
1282
- # Cache session so we can access server-assigned session_id later for DELETE
1283
- self.session = await session_manager.get_session(self._session_context, self._connection_params, "sse")
1284
- return self.session
1285
-
1286
- async def _terminate_remote_session(self) -> None:
1287
- """Attempt to explicitly terminate the remote MCP session via HTTP DELETE (best-effort)."""
1288
- # Only relevant for SSE transport
1289
- if not self._connection_params or "url" not in self._connection_params:
1290
- return
1291
-
1292
- url: str = self._connection_params["url"]
1293
-
1294
- # Retrieve session id from the underlying SDK if exposed
1295
- session_id = None
1296
- if getattr(self, "session", None) is not None:
1297
- # Common attributes in MCP python SDK: `session_id` or `id`
1298
- session_id = getattr(self.session, "session_id", None) or getattr(self.session, "id", None)
1299
-
1300
- headers: dict[str, str] = dict(self._connection_params.get("headers", {}))
1301
- if session_id:
1302
- headers["Mcp-Session-Id"] = str(session_id)
1303
-
1304
- try:
1305
- async with httpx.AsyncClient(timeout=5.0) as client:
1306
- await client.delete(url, headers=headers)
1307
- except Exception as e: # noqa: BLE001
1308
- # DELETE is advisory—log and continue
1309
- logger.debug(f"Unable to send session DELETE to '{url}': {e}")
1310
-
1311
- async def run_tool(self, tool_name: str, arguments: dict[str, Any]) -> Any:
1312
- """Run a tool with the given arguments using context-specific session.
1313
-
1314
- Args:
1315
- tool_name: Name of the tool to run
1316
- arguments: Dictionary of arguments to pass to the tool
1317
-
1318
- Returns:
1319
- The result of the tool execution
1320
-
1321
- Raises:
1322
- ValueError: If session is not initialized or tool execution fails
1323
- """
1324
- if not self._connected or not self._connection_params:
1325
- msg = "Session not initialized or disconnected. Call connect_to_server first."
1326
- raise ValueError(msg)
1327
-
1328
- # If no session context is set, create a default one
1329
- if not self._session_context:
1330
- # Generate a fallback context based on connection parameters
1331
- import uuid
1332
-
1333
- param_hash = uuid.uuid4().hex[:8]
1334
- self._session_context = f"default_sse_{param_hash}"
1335
-
1336
- max_retries = 2
1337
- last_error_type = None
1338
-
1339
- for attempt in range(max_retries):
1340
- try:
1341
- await logger.adebug(f"Attempting to run tool '{tool_name}' (attempt {attempt + 1}/{max_retries})")
1342
- # Get or create persistent session
1343
- session = await self._get_or_create_session()
1344
-
1345
- # Add timeout to prevent hanging
1346
- import asyncio
1347
-
1348
- result = await asyncio.wait_for(
1349
- session.call_tool(tool_name, arguments=arguments),
1350
- timeout=30.0, # 30 second timeout
1351
- )
1352
- except Exception as e:
1353
- current_error_type = type(e).__name__
1354
- await logger.awarning(f"Tool '{tool_name}' failed on attempt {attempt + 1}: {current_error_type} - {e}")
1355
-
1356
- # Import specific MCP error types for detection
1357
- try:
1358
- from anyio import ClosedResourceError
1359
- from mcp.shared.exceptions import McpError
1360
-
1361
- is_closed_resource_error = isinstance(e, ClosedResourceError)
1362
- is_mcp_connection_error = isinstance(e, McpError) and "Connection closed" in str(e)
1363
- except ImportError:
1364
- is_closed_resource_error = "ClosedResourceError" in str(type(e))
1365
- is_mcp_connection_error = "Connection closed" in str(e)
1366
-
1367
- # Detect timeout errors
1368
- is_timeout_error = isinstance(e, asyncio.TimeoutError | TimeoutError)
1369
-
1370
- # If we're getting the same error type repeatedly, don't retry
1371
- if last_error_type == current_error_type and attempt > 0:
1372
- await logger.aerror(f"Repeated {current_error_type} error for tool '{tool_name}', not retrying")
1373
- break
1374
-
1375
- last_error_type = current_error_type
1376
-
1377
- # If it's a connection error (ClosedResourceError or MCP connection closed) and we have retries left
1378
- if (is_closed_resource_error or is_mcp_connection_error) and attempt < max_retries - 1:
1379
- await logger.awarning(
1380
- f"MCP session connection issue for tool '{tool_name}', retrying with fresh session..."
1381
- )
1382
- # Clean up the dead session
1383
- if self._session_context:
1384
- session_manager = self._get_session_manager()
1385
- await session_manager._cleanup_session(self._session_context)
1386
- # Add a small delay before retry
1387
- await asyncio.sleep(0.5)
1388
- continue
1389
-
1390
- # If it's a timeout error and we have retries left, try once more
1391
- if is_timeout_error and attempt < max_retries - 1:
1392
- await logger.awarning(f"Tool '{tool_name}' timed out, retrying...")
1393
- # Don't clean up session for timeouts, might just be a slow response
1394
- await asyncio.sleep(1.0)
1395
- continue
1396
-
1397
- # For other errors or no retries left, handle as before
1398
- if (
1399
- isinstance(e, ConnectionError | TimeoutError | OSError | ValueError)
1400
- or is_closed_resource_error
1401
- or is_mcp_connection_error
1402
- or is_timeout_error
1403
- ):
1404
- msg = f"Failed to run tool '{tool_name}' after {attempt + 1} attempts: {e}"
1405
- await logger.aerror(msg)
1406
- # Clean up failed session from cache
1407
- if self._session_context and self._component_cache:
1408
- cache_key = f"mcp_session_sse_{self._session_context}"
1409
- self._component_cache.delete(cache_key)
1410
- self._connected = False
1411
- raise ValueError(msg) from e
1412
- # Re-raise unexpected errors
1413
- raise
1414
- else:
1415
- await logger.adebug(f"Tool '{tool_name}' completed successfully")
1416
- return result
1417
-
1418
- # This should never be reached due to the exception handling above
1419
- msg = f"Failed to run tool '{tool_name}': Maximum retries exceeded with repeated {last_error_type} errors"
1420
- await logger.aerror(msg)
1421
- raise ValueError(msg)
1422
-
1423
- async def disconnect(self):
1424
- """Properly close the connection and clean up resources."""
1425
- # Attempt best-effort remote session termination first
1426
- await self._terminate_remote_session()
1427
-
1428
- # Clean up local session using the session manager
1429
- if self._session_context:
1430
- session_manager = self._get_session_manager()
1431
- await session_manager._cleanup_session(self._session_context)
1432
-
1433
- # Reset local state
1434
- self.session = None
1435
- self._connection_params = None
1436
- self._connected = False
1437
- self._session_context = None
1438
-
1439
- async def __aenter__(self):
1440
- return self
1441
-
1442
- async def __aexit__(self, exc_type, exc_val, exc_tb):
1443
- await self.disconnect()
1444
-
1445
-
1446
- async def update_tools(
1447
- server_name: str,
1448
- server_config: dict,
1449
- mcp_stdio_client: MCPStdioClient | None = None,
1450
- mcp_sse_client: MCPSseClient | None = None,
1451
- ) -> tuple[str, list[StructuredTool], dict[str, StructuredTool]]:
1452
- """Fetch server config and update available tools."""
1453
- if server_config is None:
1454
- server_config = {}
1455
- if not server_name:
1456
- return "", [], {}
1457
- if mcp_stdio_client is None:
1458
- mcp_stdio_client = MCPStdioClient()
1459
- if mcp_sse_client is None:
1460
- mcp_sse_client = MCPSseClient()
1461
-
1462
- # Fetch server config from backend
1463
- mode = "Stdio" if "command" in server_config else "SSE" if "url" in server_config else ""
1464
- command = server_config.get("command", "")
1465
- url = server_config.get("url", "")
1466
- tools = []
1467
- headers = _process_headers(server_config.get("headers", {}))
1468
-
1469
- try:
1470
- await _validate_connection_params(mode, command, url)
1471
- except ValueError as e:
1472
- logger.error(f"Invalid MCP server configuration for '{server_name}': {e}")
1473
- raise
1474
-
1475
- # Determine connection type and parameters
1476
- client: MCPStdioClient | MCPSseClient | None = None
1477
- if mode == "Stdio":
1478
- # Stdio connection
1479
- args = server_config.get("args", [])
1480
- env = server_config.get("env", {})
1481
- full_command = " ".join([command, *args])
1482
- tools = await mcp_stdio_client.connect_to_server(full_command, env)
1483
- client = mcp_stdio_client
1484
- elif mode == "SSE":
1485
- # SSE connection
1486
- tools = await mcp_sse_client.connect_to_server(url, headers=headers)
1487
- client = mcp_sse_client
1488
- else:
1489
- logger.error(f"Invalid MCP server mode for '{server_name}': {mode}")
1490
- return "", [], {}
1491
-
1492
- if not tools or not client or not client._connected:
1493
- logger.warning(f"No tools available from MCP server '{server_name}' or connection failed")
1494
- return "", [], {}
1495
-
1496
- tool_list = []
1497
- tool_cache: dict[str, StructuredTool] = {}
1498
- for tool in tools:
1499
- if not tool or not hasattr(tool, "name"):
1500
- continue
1501
- try:
1502
- args_schema = create_input_schema_from_json_schema(tool.inputSchema)
1503
- if not args_schema:
1504
- logger.warning(f"Could not create schema for tool '{tool.name}' from server '{server_name}'")
1505
- continue
1506
-
1507
- tool_obj = StructuredTool(
1508
- name=tool.name,
1509
- description=tool.description or "",
1510
- args_schema=args_schema,
1511
- func=create_tool_func(tool.name, args_schema, client),
1512
- coroutine=create_tool_coroutine(tool.name, args_schema, client),
1513
- tags=[tool.name],
1514
- metadata={"server_name": server_name},
1515
- )
1516
- tool_list.append(tool_obj)
1517
- tool_cache[tool.name] = tool_obj
1518
- except (ConnectionError, TimeoutError, OSError, ValueError) as e:
1519
- logger.error(f"Failed to create tool '{tool.name}' from server '{server_name}': {e}")
1520
- msg = f"Failed to create tool '{tool.name}' from server '{server_name}': {e}"
1521
- raise ValueError(msg) from e
1522
-
1523
- logger.info(f"Successfully loaded {len(tool_list)} tools from MCP server '{server_name}'")
1524
- return mode, tool_list, tool_cache