sunholo 0.141.1__tar.gz → 0.143.1__tar.gz

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 (193) hide show
  1. {sunholo-0.141.1/src/sunholo.egg-info → sunholo-0.143.1}/PKG-INFO +6 -5
  2. {sunholo-0.141.1 → sunholo-0.143.1}/pyproject.toml +6 -5
  3. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/agents/flask/vac_routes.py +300 -1
  4. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/invoke/async_class.py +98 -8
  5. sunholo-0.143.1/src/sunholo/mcp/__init__.py +20 -0
  6. sunholo-0.143.1/src/sunholo/mcp/mcp_manager.py +98 -0
  7. sunholo-0.143.1/src/sunholo/mcp/vac_mcp_server.py +259 -0
  8. {sunholo-0.141.1 → sunholo-0.143.1/src/sunholo.egg-info}/PKG-INFO +6 -5
  9. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo.egg-info/SOURCES.txt +4 -1
  10. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo.egg-info/requires.txt +5 -4
  11. sunholo-0.143.1/tests/test_vac_routes_mcp.py +339 -0
  12. sunholo-0.141.1/src/sunholo/templates/system_services/__init__.py +0 -0
  13. {sunholo-0.141.1 → sunholo-0.143.1}/LICENSE.txt +0 -0
  14. {sunholo-0.141.1 → sunholo-0.143.1}/MANIFEST.in +0 -0
  15. {sunholo-0.141.1 → sunholo-0.143.1}/README.md +0 -0
  16. {sunholo-0.141.1 → sunholo-0.143.1}/setup.cfg +0 -0
  17. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/__init__.py +0 -0
  18. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/agents/__init__.py +0 -0
  19. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/agents/chat_history.py +0 -0
  20. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/agents/dispatch_to_qa.py +0 -0
  21. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/agents/fastapi/__init__.py +0 -0
  22. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/agents/fastapi/base.py +0 -0
  23. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/agents/fastapi/qna_routes.py +0 -0
  24. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/agents/flask/__init__.py +0 -0
  25. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/agents/flask/base.py +0 -0
  26. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/agents/langserve.py +0 -0
  27. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/agents/pubsub.py +0 -0
  28. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/agents/route.py +0 -0
  29. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/agents/special_commands.py +0 -0
  30. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/agents/swagger.py +0 -0
  31. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/archive/__init__.py +0 -0
  32. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/archive/archive.py +0 -0
  33. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/auth/__init__.py +0 -0
  34. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/auth/gcloud.py +0 -0
  35. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/auth/refresh.py +0 -0
  36. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/auth/run.py +0 -0
  37. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/azure/__init__.py +0 -0
  38. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/azure/auth.py +0 -0
  39. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/azure/blobs.py +0 -0
  40. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/azure/event_grid.py +0 -0
  41. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/bots/__init__.py +0 -0
  42. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/bots/discord.py +0 -0
  43. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/bots/github_webhook.py +0 -0
  44. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/bots/webapp.py +0 -0
  45. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/chunker/__init__.py +0 -0
  46. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/chunker/azure.py +0 -0
  47. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/chunker/doc_handling.py +0 -0
  48. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/chunker/encode_metadata.py +0 -0
  49. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/chunker/images.py +0 -0
  50. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/chunker/loaders.py +0 -0
  51. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/chunker/message_data.py +0 -0
  52. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/chunker/pdfs.py +0 -0
  53. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/chunker/process_chunker_data.py +0 -0
  54. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/chunker/publish.py +0 -0
  55. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/chunker/pubsub.py +0 -0
  56. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/chunker/splitter.py +0 -0
  57. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/cli/__init__.py +0 -0
  58. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/cli/chat_vac.py +0 -0
  59. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/cli/cli.py +0 -0
  60. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/cli/cli_init.py +0 -0
  61. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/cli/configs.py +0 -0
  62. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/cli/deploy.py +0 -0
  63. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/cli/embedder.py +0 -0
  64. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/cli/merge_texts.py +0 -0
  65. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/cli/run_proxy.py +0 -0
  66. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/cli/sun_rich.py +0 -0
  67. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/cli/swagger.py +0 -0
  68. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/cli/vertex.py +0 -0
  69. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/components/__init__.py +0 -0
  70. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/components/llm.py +0 -0
  71. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/components/retriever.py +0 -0
  72. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/components/vectorstore.py +0 -0
  73. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/custom_logging.py +0 -0
  74. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/database/__init__.py +0 -0
  75. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/database/alloydb.py +0 -0
  76. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/database/alloydb_client.py +0 -0
  77. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/database/database.py +0 -0
  78. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/database/lancedb.py +0 -0
  79. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/database/sql/sb/create_function.sql +0 -0
  80. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/database/sql/sb/create_function_time.sql +0 -0
  81. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/database/sql/sb/create_table.sql +0 -0
  82. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/database/sql/sb/delete_source_row.sql +0 -0
  83. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/database/sql/sb/return_sources.sql +0 -0
  84. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/database/sql/sb/setup.sql +0 -0
  85. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/database/static_dbs.py +0 -0
  86. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/database/uuid.py +0 -0
  87. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/discovery_engine/__init__.py +0 -0
  88. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/discovery_engine/chunker_handler.py +0 -0
  89. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/discovery_engine/cli.py +0 -0
  90. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/discovery_engine/create_new.py +0 -0
  91. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/discovery_engine/discovery_engine_client.py +0 -0
  92. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/discovery_engine/get_ai_search_chunks.py +0 -0
  93. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/embedder/__init__.py +0 -0
  94. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/embedder/embed_chunk.py +0 -0
  95. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/embedder/embed_metadata.py +0 -0
  96. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/excel/__init__.py +0 -0
  97. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/excel/plugin.py +0 -0
  98. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/gcs/__init__.py +0 -0
  99. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/gcs/add_file.py +0 -0
  100. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/gcs/download_folder.py +0 -0
  101. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/gcs/download_gcs_text.py +0 -0
  102. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/gcs/download_url.py +0 -0
  103. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/gcs/extract_and_sign.py +0 -0
  104. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/gcs/metadata.py +0 -0
  105. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/genai/__init__.py +0 -0
  106. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/genai/file_handling.py +0 -0
  107. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/genai/genaiv2.py +0 -0
  108. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/genai/images.py +0 -0
  109. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/genai/init.py +0 -0
  110. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/genai/process_funcs_cls.py +0 -0
  111. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/genai/safety.py +0 -0
  112. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/invoke/__init__.py +0 -0
  113. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/invoke/direct_vac_func.py +0 -0
  114. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/invoke/invoke_vac_utils.py +0 -0
  115. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/langchain_types.py +0 -0
  116. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/langfuse/__init__.py +0 -0
  117. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/langfuse/callback.py +0 -0
  118. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/langfuse/evals.py +0 -0
  119. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/langfuse/prompts.py +0 -0
  120. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/llamaindex/__init__.py +0 -0
  121. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/llamaindex/get_files.py +0 -0
  122. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/llamaindex/import_files.py +0 -0
  123. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/llamaindex/llamaindex_class.py +0 -0
  124. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/llamaindex/user_history.py +0 -0
  125. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/lookup/__init__.py +0 -0
  126. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/lookup/model_lookup.yaml +0 -0
  127. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/mcp/cli.py +0 -0
  128. {sunholo-0.141.1/src/sunholo/mcp → sunholo-0.143.1/src/sunholo/ollama}/__init__.py +0 -0
  129. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/ollama/ollama_images.py +0 -0
  130. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/pubsub/__init__.py +0 -0
  131. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/pubsub/process_pubsub.py +0 -0
  132. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/pubsub/pubsub_manager.py +0 -0
  133. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/qna/__init__.py +0 -0
  134. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/qna/parsers.py +0 -0
  135. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/qna/retry.py +0 -0
  136. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/senses/__init__.py +0 -0
  137. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/senses/stream_voice.py +0 -0
  138. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/streaming/__init__.py +0 -0
  139. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/streaming/content_buffer.py +0 -0
  140. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/streaming/langserve.py +0 -0
  141. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/streaming/stream_lookup.py +0 -0
  142. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/streaming/streaming.py +0 -0
  143. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/summarise/__init__.py +0 -0
  144. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/summarise/summarise.py +0 -0
  145. {sunholo-0.141.1/src/sunholo/ollama → sunholo-0.143.1/src/sunholo/templates/agent}/__init__.py +0 -0
  146. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/templates/agent/agent_service.py +0 -0
  147. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/templates/agent/app.py +0 -0
  148. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/templates/agent/my_log.py +0 -0
  149. {sunholo-0.141.1/src/sunholo/templates/agent → sunholo-0.143.1/src/sunholo/templates/agent/tools}/__init__.py +0 -0
  150. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/templates/agent/tools/your_agent.py +0 -0
  151. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/templates/agent/vac_service.py +0 -0
  152. {sunholo-0.141.1/src/sunholo/templates/agent/tools → sunholo-0.143.1/src/sunholo/templates/project}/__init__.py +0 -0
  153. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/templates/project/app.py +0 -0
  154. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/templates/project/my_log.py +0 -0
  155. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/templates/project/vac_service.py +0 -0
  156. {sunholo-0.141.1/src/sunholo/templates/project → sunholo-0.143.1/src/sunholo/templates/system_services}/__init__.py +0 -0
  157. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/templates/system_services/app.py +0 -0
  158. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/templates/system_services/my_log.py +0 -0
  159. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/terraform/__init__.py +0 -0
  160. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/terraform/tfvars_editor.py +0 -0
  161. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/tools/__init__.py +0 -0
  162. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/tools/web_browser.py +0 -0
  163. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/utils/__init__.py +0 -0
  164. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/utils/api_key.py +0 -0
  165. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/utils/big_context.py +0 -0
  166. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/utils/config.py +0 -0
  167. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/utils/config_class.py +0 -0
  168. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/utils/config_schema.py +0 -0
  169. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/utils/gcp.py +0 -0
  170. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/utils/gcp_project.py +0 -0
  171. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/utils/mime.py +0 -0
  172. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/utils/parsers.py +0 -0
  173. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/utils/proto_convert.py +0 -0
  174. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/utils/timedelta.py +0 -0
  175. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/utils/user_ids.py +0 -0
  176. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/utils/version.py +0 -0
  177. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/vertex/__init__.py +0 -0
  178. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/vertex/extensions_call.py +0 -0
  179. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/vertex/extensions_class.py +0 -0
  180. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/vertex/genai_functions.py +0 -0
  181. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/vertex/init.py +0 -0
  182. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/vertex/memory_tools.py +0 -0
  183. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/vertex/safety.py +0 -0
  184. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo/vertex/type_dict_to_json.py +0 -0
  185. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo.egg-info/dependency_links.txt +0 -0
  186. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo.egg-info/entry_points.txt +0 -0
  187. {sunholo-0.141.1 → sunholo-0.143.1}/src/sunholo.egg-info/top_level.txt +0 -0
  188. {sunholo-0.141.1 → sunholo-0.143.1}/tests/test_async.py +0 -0
  189. {sunholo-0.141.1 → sunholo-0.143.1}/tests/test_async_genai2.py +0 -0
  190. {sunholo-0.141.1 → sunholo-0.143.1}/tests/test_chat_history.py +0 -0
  191. {sunholo-0.141.1 → sunholo-0.143.1}/tests/test_config.py +0 -0
  192. {sunholo-0.141.1 → sunholo-0.143.1}/tests/test_genai2.py +0 -0
  193. {sunholo-0.141.1 → sunholo-0.143.1}/tests/test_unstructured.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sunholo
3
- Version: 0.141.1
3
+ Version: 0.143.1
4
4
  Summary: AI DevOps - a package to help deploy GenAI to the Cloud.
5
5
  Author-email: Holosun ApS <multivac@sunholo.com>
6
6
  License: Apache License, Version 2.0
@@ -19,10 +19,11 @@ Requires-Python: >=3.10
19
19
  Description-Content-Type: text/markdown
20
20
  License-File: LICENSE.txt
21
21
  Requires-Dist: aiohttp
22
+ Requires-Dist: flask>=3.1.0
22
23
  Requires-Dist: google-auth
23
- Requires-Dist: ollama>=0.4.7
24
- Requires-Dist: pillow>=11.0.0
24
+ Requires-Dist: mcp>=1.1.1
25
25
  Requires-Dist: pydantic
26
+ Requires-Dist: pytest-asyncio>=1.0.0
26
27
  Requires-Dist: requests
27
28
  Requires-Dist: ruamel.yaml
28
29
  Requires-Dist: tenacity
@@ -69,7 +70,7 @@ Requires-Dist: langchain-anthropic>=0.1.23; extra == "all"
69
70
  Requires-Dist: langchain-google-vertexai; extra == "all"
70
71
  Requires-Dist: langchain-unstructured; extra == "all"
71
72
  Requires-Dist: langfuse; extra == "all"
72
- Requires-Dist: mcp; extra == "all"
73
+ Requires-Dist: mcp>=1.1.1; extra == "all"
73
74
  Requires-Dist: numpy; extra == "all"
74
75
  Requires-Dist: opencv-python; extra == "all"
75
76
  Requires-Dist: pg8000; extra == "all"
@@ -155,7 +156,7 @@ Requires-Dist: langchain-openai>=0.3.2; extra == "openai"
155
156
  Requires-Dist: tiktoken; extra == "openai"
156
157
  Provides-Extra: anthropic
157
158
  Requires-Dist: langchain-anthropic>=0.1.23; extra == "anthropic"
158
- Requires-Dist: mcp; extra == "anthropic"
159
+ Requires-Dist: mcp>=1.1.1; extra == "anthropic"
159
160
  Provides-Extra: tools
160
161
  Requires-Dist: openapi-spec-validator; extra == "tools"
161
162
  Requires-Dist: playwright; extra == "tools"
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "sunholo"
7
- version = "0.141.1"
7
+ version = "0.143.1"
8
8
  description = "AI DevOps - a package to help deploy GenAI to the Cloud."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -26,10 +26,11 @@ classifiers = [
26
26
 
27
27
  dependencies = [
28
28
  "aiohttp",
29
+ "flask>=3.1.0",
29
30
  "google-auth",
30
- "ollama>=0.4.7",
31
- "pillow>=11.0.0",
31
+ "mcp>=1.1.1",
32
32
  "pydantic",
33
+ "pytest-asyncio>=1.0.0",
33
34
  "requests",
34
35
  "ruamel.yaml",
35
36
  "tenacity",
@@ -100,7 +101,7 @@ all = [
100
101
  "langchain-google-vertexai",
101
102
  "langchain-unstructured",
102
103
  "langfuse",
103
- "mcp",
104
+ "mcp>=1.1.1",
104
105
  "numpy",
105
106
  "opencv-python",
106
107
  "pg8000",
@@ -195,7 +196,7 @@ openai = [
195
196
  ]
196
197
  anthropic = [
197
198
  "langchain-anthropic>=0.1.23",
198
- "mcp"
199
+ "mcp>=1.1.1"
199
200
  ]
200
201
  tools = [
201
202
  "openapi-spec-validator",
@@ -6,6 +6,8 @@ import random
6
6
  from functools import partial
7
7
  import inspect
8
8
  import asyncio
9
+ from typing import Dict, List, Optional, Callable, Any
10
+
9
11
 
10
12
  from ..chat_history import extract_chat_history_with_cache, extract_chat_history_async_cached
11
13
  from ...qna.parsers import parse_output
@@ -29,6 +31,21 @@ try:
29
31
  except ImportError:
30
32
  PubSubManager = None
31
33
 
34
+ try:
35
+ from ...mcp.mcp_manager import MCPClientManager
36
+ except ImportError:
37
+ MCPClientManager = None
38
+
39
+ try:
40
+ from ...mcp.vac_mcp_server import VACMCPServer
41
+ from mcp.server.models import InitializationOptions
42
+ from mcp import JSONRPCMessage, ErrorData, INTERNAL_ERROR
43
+ except ImportError:
44
+ VACMCPServer = None
45
+ InitializationOptions = None
46
+ JSONRPCMessage = None
47
+
48
+
32
49
  # Cache dictionary to store validated API keys
33
50
  api_key_cache = {}
34
51
  cache_duration = timedelta(minutes=5) # Cache duration
@@ -61,11 +78,30 @@ if __name__ == "__main__":
61
78
  stream_interpreter: callable,
62
79
  vac_interpreter:callable=None,
63
80
  additional_routes:dict=None,
81
+ mcp_servers: List[Dict[str, Any]] = None,
64
82
  async_stream:bool=False,
65
- add_langfuse_eval:bool=True):
83
+ add_langfuse_eval:bool=True,
84
+ enable_mcp_server:bool=False):
66
85
  self.app = app
67
86
  self.stream_interpreter = stream_interpreter
68
87
  self.vac_interpreter = vac_interpreter or partial(self.vac_interpreter_default)
88
+
89
+ # MCP client initialization
90
+ self.mcp_servers = mcp_servers or []
91
+ self.mcp_client_manager = MCPClientManager()
92
+ # Initialize MCP connections
93
+ if self.mcp_servers and self.mcp_client_manager:
94
+ asyncio.create_task(self._initialize_mcp_servers())
95
+
96
+ # MCP server initialization
97
+ self.enable_mcp_server = enable_mcp_server
98
+ self.vac_mcp_server = None
99
+ if self.enable_mcp_server and VACMCPServer:
100
+ self.vac_mcp_server = VACMCPServer(
101
+ stream_interpreter=self.stream_interpreter,
102
+ vac_interpreter=self.vac_interpreter
103
+ )
104
+
69
105
  self.additional_routes = additional_routes if additional_routes is not None else []
70
106
  self.async_stream = async_stream
71
107
  self.add_langfuse_eval = add_langfuse_eval
@@ -129,6 +165,18 @@ if __name__ == "__main__":
129
165
  # OpenAI compatible endpoint
130
166
  self.app.route('/openai/v1/chat/completions', methods=['POST'])(self.handle_openai_compatible_endpoint)
131
167
  self.app.route('/openai/v1/chat/completions/<vector_name>', methods=['POST'])(self.handle_openai_compatible_endpoint)
168
+
169
+ # MCP client routes
170
+ if self.mcp_servers:
171
+ self.app.route('/mcp/tools', methods=['GET'])(self.handle_mcp_list_tools)
172
+ self.app.route('/mcp/tools/<server_name>', methods=['GET'])(self.handle_mcp_list_tools)
173
+ self.app.route('/mcp/call', methods=['POST'])(self.handle_mcp_call_tool)
174
+ self.app.route('/mcp/resources', methods=['GET'])(self.handle_mcp_list_resources)
175
+ self.app.route('/mcp/resources/read', methods=['POST'])(self.handle_mcp_read_resource)
176
+
177
+ # MCP server endpoint
178
+ if self.enable_mcp_server and self.vac_mcp_server:
179
+ self.app.route('/mcp', methods=['POST', 'GET'])(self.handle_mcp_server)
132
180
 
133
181
  self.register_additional_routes()
134
182
 
@@ -803,3 +851,254 @@ if __name__ == "__main__":
803
851
  except Exception as e:
804
852
  raise Exception(f'File upload failed: {str(e)}')
805
853
 
854
+ async def _initialize_mcp_servers(self):
855
+ """Initialize connections to configured MCP servers."""
856
+ for server_config in self.mcp_servers:
857
+ try:
858
+ await self.mcp_client_manager.connect_to_server(
859
+ server_name=server_config["name"],
860
+ command=server_config["command"],
861
+ args=server_config.get("args", [])
862
+ )
863
+ log.info(f"Connected to MCP server: {server_config['name']}")
864
+ except Exception as e:
865
+ log.error(f"Failed to connect to MCP server {server_config['name']}: {e}")
866
+
867
+
868
+ def handle_mcp_list_tools(self, server_name: Optional[str] = None):
869
+ """List available MCP tools."""
870
+ async def get_tools():
871
+ tools = await self.mcp_client_manager.list_tools(server_name)
872
+ return [
873
+ {
874
+ "name": tool.name,
875
+ "description": tool.description,
876
+ "inputSchema": tool.inputSchema,
877
+ "server": tool.metadata.get("server") if tool.metadata else server_name
878
+ }
879
+ for tool in tools
880
+ ]
881
+
882
+ # Run async in sync context
883
+ loop = asyncio.new_event_loop()
884
+ asyncio.set_event_loop(loop)
885
+ try:
886
+ tools = loop.run_until_complete(get_tools())
887
+ return jsonify({"tools": tools})
888
+ finally:
889
+ loop.close()
890
+
891
+ def handle_mcp_call_tool(self):
892
+ """Call an MCP tool."""
893
+ data = request.get_json()
894
+ server_name = data.get("server")
895
+ tool_name = data.get("tool")
896
+ arguments = data.get("arguments", {})
897
+
898
+ if not server_name or not tool_name:
899
+ return jsonify({"error": "Missing 'server' or 'tool' parameter"}), 400
900
+
901
+ async def call_tool():
902
+ try:
903
+ result = await self.mcp_client_manager.call_tool(server_name, tool_name, arguments)
904
+
905
+ # Convert result to JSON-serializable format
906
+ if hasattr(result, 'content'):
907
+ # Handle different content types
908
+ if hasattr(result.content, 'text'):
909
+ return {"result": result.content.text}
910
+ elif hasattr(result.content, 'data'):
911
+ return {"result": result.content.data}
912
+ else:
913
+ return {"result": str(result.content)}
914
+ else:
915
+ return {"result": str(result)}
916
+
917
+ except Exception as e:
918
+ return {"error": str(e)}
919
+
920
+ loop = asyncio.new_event_loop()
921
+ asyncio.set_event_loop(loop)
922
+ try:
923
+ result = loop.run_until_complete(call_tool())
924
+ if "error" in result:
925
+ return jsonify(result), 500
926
+ return jsonify(result)
927
+ finally:
928
+ loop.close()
929
+
930
+ def handle_mcp_list_resources(self):
931
+ """List available MCP resources."""
932
+ server_name = request.args.get("server")
933
+
934
+ async def get_resources():
935
+ resources = await self.mcp_client_manager.list_resources(server_name)
936
+ return [
937
+ {
938
+ "uri": resource.uri,
939
+ "name": resource.name,
940
+ "description": resource.description,
941
+ "mimeType": resource.mimeType,
942
+ "server": resource.metadata.get("server") if resource.metadata else server_name
943
+ }
944
+ for resource in resources
945
+ ]
946
+
947
+ loop = asyncio.new_event_loop()
948
+ asyncio.set_event_loop(loop)
949
+ try:
950
+ resources = loop.run_until_complete(get_resources())
951
+ return jsonify({"resources": resources})
952
+ finally:
953
+ loop.close()
954
+
955
+ def handle_mcp_read_resource(self):
956
+ """Read an MCP resource."""
957
+ data = request.get_json()
958
+ server_name = data.get("server")
959
+ uri = data.get("uri")
960
+
961
+ if not server_name or not uri:
962
+ return jsonify({"error": "Missing 'server' or 'uri' parameter"}), 400
963
+
964
+ async def read_resource():
965
+ try:
966
+ contents = await self.mcp_client_manager.read_resource(server_name, uri)
967
+ return {
968
+ "contents": [
969
+ {"text": content.text} if hasattr(content, 'text') else {"data": str(content)}
970
+ for content in contents
971
+ ]
972
+ }
973
+ except Exception as e:
974
+ return {"error": str(e)}
975
+
976
+ loop = asyncio.new_event_loop()
977
+ asyncio.set_event_loop(loop)
978
+ try:
979
+ result = loop.run_until_complete(read_resource())
980
+ if "error" in result:
981
+ return jsonify(result), 500
982
+ return jsonify(result)
983
+ finally:
984
+ loop.close()
985
+
986
+ def handle_mcp_server(self):
987
+ """Handle MCP server requests using HTTP transport."""
988
+ if not self.vac_mcp_server:
989
+ return jsonify({"error": "MCP server not enabled"}), 501
990
+
991
+ import json as json_module
992
+
993
+ # Handle streaming for HTTP transport
994
+ if request.method == 'POST':
995
+ try:
996
+ # Get the JSON-RPC request
997
+ data = request.get_json()
998
+ log.info(f"MCP server received: {data}")
999
+
1000
+ # Create an async handler for the request
1001
+ async def process_request():
1002
+ # Create mock read/write streams for the server
1003
+ from io import StringIO
1004
+ import asyncio
1005
+
1006
+ # Convert request to proper format
1007
+ request_str = json_module.dumps(data) + '\n'
1008
+
1009
+ # Create read queue with the request
1010
+ read_queue = asyncio.Queue()
1011
+ await read_queue.put(request_str.encode())
1012
+ await read_queue.put(None) # EOF signal
1013
+
1014
+ # Create write queue for response
1015
+ write_queue = asyncio.Queue()
1016
+
1017
+ # Create async iterators
1018
+ async def read_messages():
1019
+ while True:
1020
+ msg = await read_queue.get()
1021
+ if msg is None:
1022
+ break
1023
+ yield msg
1024
+
1025
+ responses = []
1026
+ async def write_messages():
1027
+ async for msg in write_queue:
1028
+ if msg is None:
1029
+ break
1030
+ responses.append(msg.decode())
1031
+
1032
+ # Run the server with these streams
1033
+ server = self.vac_mcp_server.get_server()
1034
+
1035
+ # Start write handler
1036
+ write_task = asyncio.create_task(write_messages())
1037
+
1038
+ try:
1039
+ # Process the request through the server
1040
+ await server.run(
1041
+ read_messages(),
1042
+ write_queue,
1043
+ InitializationOptions() if InitializationOptions else None
1044
+ )
1045
+ except Exception as e:
1046
+ log.error(f"Error processing MCP request: {e}")
1047
+ await write_queue.put(None)
1048
+ await write_task
1049
+ raise
1050
+
1051
+ # Signal end and wait for write task
1052
+ await write_queue.put(None)
1053
+ await write_task
1054
+
1055
+ # Return collected responses
1056
+ return responses
1057
+
1058
+ # Run the async handler
1059
+ loop = asyncio.new_event_loop()
1060
+ asyncio.set_event_loop(loop)
1061
+ try:
1062
+ responses = loop.run_until_complete(process_request())
1063
+
1064
+ # Parse and return the response
1065
+ if responses:
1066
+ # The response should be a single JSON-RPC response
1067
+ response_data = json_module.loads(responses[0])
1068
+ return jsonify(response_data)
1069
+ else:
1070
+ return jsonify({"error": "No response from MCP server"}), 500
1071
+
1072
+ except Exception as e:
1073
+ log.error(f"MCP server error: {str(e)}")
1074
+ return jsonify({
1075
+ "jsonrpc": "2.0",
1076
+ "error": {
1077
+ "code": -32603,
1078
+ "message": f"Internal error: {str(e)}"
1079
+ },
1080
+ "id": data.get("id") if isinstance(data, dict) else None
1081
+ }), 500
1082
+ finally:
1083
+ loop.close()
1084
+
1085
+ except Exception as e:
1086
+ log.error(f"MCP server error: {str(e)}")
1087
+ return jsonify({
1088
+ "jsonrpc": "2.0",
1089
+ "error": {
1090
+ "code": -32603,
1091
+ "message": f"Internal error: {str(e)}"
1092
+ },
1093
+ "id": data.get("id") if isinstance(data, dict) else None
1094
+ }), 500
1095
+
1096
+ else:
1097
+ # GET request - return server information
1098
+ return jsonify({
1099
+ "name": "sunholo-vac-server",
1100
+ "version": "1.0.0",
1101
+ "transport": "http",
1102
+ "endpoint": "/mcp",
1103
+ "tools": ["vac_stream", "vac_query"] if self.vac_interpreter else ["vac_stream"]
1104
+ })
@@ -8,12 +8,39 @@ from tenacity import AsyncRetrying, retry_if_exception_type, wait_random_exponen
8
8
  log = setup_logging("sunholo_AsyncTaskRunner")
9
9
 
10
10
  class AsyncTaskRunner:
11
- def __init__(self, retry_enabled:bool=False, retry_kwargs:dict=None, timeout:int=120, max_concurrency:int=20):
11
+ def __init__(self,
12
+ retry_enabled: bool = False,
13
+ retry_kwargs: dict = None,
14
+ timeout: int = 120,
15
+ max_concurrency: int = 20,
16
+ heartbeat_extends_timeout: bool = False,
17
+ hard_timeout: int = None):
18
+ """
19
+ Initialize AsyncTaskRunner with configurable timeout behavior.
20
+
21
+ Args:
22
+ retry_enabled: Whether to enable retries
23
+ retry_kwargs: Retry configuration
24
+ timeout: Base timeout for tasks (seconds)
25
+ max_concurrency: Maximum concurrent tasks
26
+ heartbeat_extends_timeout: If True, heartbeats reset the timeout timer
27
+ hard_timeout: Maximum absolute timeout regardless of heartbeats (seconds).
28
+ If None, defaults to timeout * 5 when heartbeat_extends_timeout=True
29
+ """
12
30
  self.tasks = []
13
31
  self.retry_enabled = retry_enabled
14
32
  self.retry_kwargs = retry_kwargs or {}
15
33
  self.timeout = timeout
16
34
  self.semaphore = asyncio.Semaphore(max_concurrency)
35
+ self.heartbeat_extends_timeout = heartbeat_extends_timeout
36
+
37
+ # Set hard timeout
38
+ if hard_timeout is not None:
39
+ self.hard_timeout = hard_timeout
40
+ elif heartbeat_extends_timeout:
41
+ self.hard_timeout = timeout * 5 # Default to 5x base timeout
42
+ else:
43
+ self.hard_timeout = timeout # Same as regular timeout
17
44
 
18
45
  def add_task(self, func: Callable[..., Any], *args: Any, **kwargs: Any):
19
46
  """
@@ -39,9 +66,11 @@ class AsyncTaskRunner:
39
66
  for name, func, args, kwargs in self.tasks:
40
67
  log.info(f"Executing task: {name=}, {func=} with args: {args}, kwargs: {kwargs}")
41
68
  completion_event = asyncio.Event()
42
- task_coro = self._run_with_retries_and_timeout(name, func, args, kwargs, queue, completion_event)
69
+ last_heartbeat = {'time': time.time()} # Shared mutable object for heartbeat tracking
70
+
71
+ task_coro = self._run_with_retries_and_timeout(name, func, args, kwargs, queue, completion_event, last_heartbeat)
43
72
  task = asyncio.create_task(task_coro)
44
- heartbeat_coro = self._send_heartbeat(name, completion_event, queue)
73
+ heartbeat_coro = self._send_heartbeat(name, completion_event, queue, last_heartbeat)
45
74
  heartbeat_task = asyncio.create_task(heartbeat_coro)
46
75
  task_infos.append({
47
76
  'name': name,
@@ -93,9 +122,12 @@ class AsyncTaskRunner:
93
122
  args: tuple,
94
123
  kwargs: dict,
95
124
  queue: asyncio.Queue,
96
- completion_event: asyncio.Event) -> None:
125
+ completion_event: asyncio.Event,
126
+ last_heartbeat: dict) -> None:
97
127
  try:
98
128
  log.info(f"run_with_retries_and_timeout: {name=}, {func=} with args: {args}, kwargs: {kwargs}")
129
+ log.info(f"Timeout mode: heartbeat_extends_timeout={self.heartbeat_extends_timeout}, timeout={self.timeout}s, hard_timeout={self.hard_timeout}s")
130
+
99
131
  if self.retry_enabled:
100
132
  retry_kwargs = {
101
133
  'wait': wait_random_exponential(multiplier=1, max=60),
@@ -106,13 +138,13 @@ class AsyncTaskRunner:
106
138
  async for attempt in AsyncRetrying(**retry_kwargs):
107
139
  with attempt:
108
140
  log.info(f"Starting task '{name}' with retry")
109
- result = await asyncio.wait_for(self._execute_task(func, *args, **kwargs), timeout=self.timeout)
141
+ result = await self._execute_task_with_timeout(func, name, last_heartbeat, *args, **kwargs)
110
142
  await queue.put({'type': 'task_complete', 'func_name': name, 'result': result})
111
143
  log.info(f"Sent 'task_complete' message for task '{name}'")
112
144
  return
113
145
  else:
114
146
  log.info(f"Starting task '{name}' with no retry")
115
- result = await asyncio.wait_for(self._execute_task(func, *args, **kwargs), timeout=self.timeout)
147
+ result = await self._execute_task_with_timeout(func, name, last_heartbeat, *args, **kwargs)
116
148
  await queue.put({'type': 'task_complete', 'func_name': name, 'result': result})
117
149
  log.info(f"Sent 'task_complete' message for task '{name}'")
118
150
  except asyncio.TimeoutError:
@@ -125,6 +157,55 @@ class AsyncTaskRunner:
125
157
  log.info(f"Task '{name}' completed.")
126
158
  completion_event.set()
127
159
 
160
+ async def _execute_task_with_timeout(self, func: Callable[..., Any], name: str, last_heartbeat: dict, *args: Any, **kwargs: Any) -> Any:
161
+ """
162
+ Execute task with either fixed timeout or heartbeat-extendable timeout.
163
+ """
164
+ if not self.heartbeat_extends_timeout:
165
+ # Original behavior - fixed timeout
166
+ return await asyncio.wait_for(self._execute_task(func, *args, **kwargs), timeout=self.timeout)
167
+ else:
168
+ # New behavior - heartbeat extends timeout
169
+ return await self._execute_task_with_heartbeat_timeout(func, name, last_heartbeat, *args, **kwargs)
170
+
171
+ async def _execute_task_with_heartbeat_timeout(self, func: Callable[..., Any], name: str, last_heartbeat: dict, *args: Any, **kwargs: Any) -> Any:
172
+ """
173
+ Execute task with heartbeat-extendable timeout and hard timeout limit.
174
+ """
175
+ start_time = time.time()
176
+ task = asyncio.create_task(self._execute_task(func, *args, **kwargs))
177
+
178
+ while not task.done():
179
+ current_time = time.time()
180
+
181
+ # Check hard timeout first (absolute limit)
182
+ if current_time - start_time > self.hard_timeout:
183
+ task.cancel()
184
+ try:
185
+ await task
186
+ except asyncio.CancelledError:
187
+ pass
188
+ raise asyncio.TimeoutError(f"Hard timeout exceeded ({self.hard_timeout}s)")
189
+
190
+ # Check soft timeout (extends with heartbeats)
191
+ time_since_heartbeat = current_time - last_heartbeat['time']
192
+ if time_since_heartbeat > self.timeout:
193
+ task.cancel()
194
+ try:
195
+ await task
196
+ except asyncio.CancelledError:
197
+ pass
198
+ raise asyncio.TimeoutError(f"Timeout exceeded - no heartbeat for {self.timeout}s")
199
+
200
+ # Wait a bit before checking again
201
+ try:
202
+ await asyncio.wait_for(asyncio.shield(task), timeout=1.0)
203
+ break # Task completed
204
+ except asyncio.TimeoutError:
205
+ continue # Check timeouts again
206
+
207
+ return await task
208
+
128
209
  async def _execute_task(self, func: Callable[..., Any], *args: Any, **kwargs: Any) -> Any:
129
210
  """
130
211
  Executes the given task function and returns its result.
@@ -143,14 +224,16 @@ class AsyncTaskRunner:
143
224
  else:
144
225
  return await asyncio.to_thread(func, *args, **kwargs)
145
226
 
146
- async def _send_heartbeat(self, func_name: str, completion_event: asyncio.Event, queue: asyncio.Queue, interval: int = 2):
227
+ async def _send_heartbeat(self, func_name: str, completion_event: asyncio.Event, queue: asyncio.Queue, last_heartbeat: dict, interval: int = 2):
147
228
  """
148
229
  Sends periodic heartbeat updates to indicate the task is still in progress.
230
+ Updates last_heartbeat time if heartbeat_extends_timeout is enabled.
149
231
 
150
232
  Args:
151
233
  func_name (str): The name of the task function.
152
234
  completion_event (asyncio.Event): Event to signal when the task is completed.
153
235
  queue (asyncio.Queue): The queue to send heartbeat messages to.
236
+ last_heartbeat (dict): Mutable dict containing the last heartbeat time.
154
237
  interval (int): How frequently to send heartbeat messages (in seconds).
155
238
  """
156
239
  start_time = time.time()
@@ -158,7 +241,14 @@ class AsyncTaskRunner:
158
241
  try:
159
242
  while not completion_event.is_set():
160
243
  await asyncio.sleep(interval)
161
- elapsed_time = int(time.time() - start_time)
244
+ current_time = time.time()
245
+ elapsed_time = int(current_time - start_time)
246
+
247
+ # Update last heartbeat time if heartbeat extends timeout
248
+ if self.heartbeat_extends_timeout:
249
+ last_heartbeat['time'] = current_time
250
+ log.debug(f"Updated heartbeat time for task '{func_name}' at {current_time}")
251
+
162
252
  heartbeat_message = {
163
253
  'type': 'heartbeat',
164
254
  'name': func_name,
@@ -0,0 +1,20 @@
1
+ # Copyright [2024] [Holosun ApS]
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """MCP (Model Context Protocol) integration for Sunholo."""
16
+
17
+ from .mcp_manager import MCPClientManager
18
+ from .vac_mcp_server import VACMCPServer
19
+
20
+ __all__ = ['MCPClientManager', 'VACMCPServer']