ai-parrot 0.17.2__cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.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 (535) hide show
  1. agentui/.prettierrc +15 -0
  2. agentui/QUICKSTART.md +272 -0
  3. agentui/README.md +59 -0
  4. agentui/env.example +16 -0
  5. agentui/jsconfig.json +14 -0
  6. agentui/package-lock.json +4242 -0
  7. agentui/package.json +34 -0
  8. agentui/scripts/postinstall/apply-patches.mjs +260 -0
  9. agentui/src/app.css +61 -0
  10. agentui/src/app.d.ts +13 -0
  11. agentui/src/app.html +12 -0
  12. agentui/src/components/LoadingSpinner.svelte +64 -0
  13. agentui/src/components/ThemeSwitcher.svelte +159 -0
  14. agentui/src/components/index.js +4 -0
  15. agentui/src/lib/api/bots.ts +60 -0
  16. agentui/src/lib/api/chat.ts +22 -0
  17. agentui/src/lib/api/http.ts +25 -0
  18. agentui/src/lib/components/BotCard.svelte +33 -0
  19. agentui/src/lib/components/ChatBubble.svelte +63 -0
  20. agentui/src/lib/components/Toast.svelte +21 -0
  21. agentui/src/lib/config.ts +20 -0
  22. agentui/src/lib/stores/auth.svelte.ts +73 -0
  23. agentui/src/lib/stores/theme.svelte.js +64 -0
  24. agentui/src/lib/stores/toast.svelte.ts +31 -0
  25. agentui/src/lib/utils/conversation.ts +39 -0
  26. agentui/src/routes/+layout.svelte +20 -0
  27. agentui/src/routes/+page.svelte +232 -0
  28. agentui/src/routes/login/+page.svelte +200 -0
  29. agentui/src/routes/talk/[agentId]/+page.svelte +297 -0
  30. agentui/src/routes/talk/[agentId]/+page.ts +7 -0
  31. agentui/static/README.md +1 -0
  32. agentui/svelte.config.js +11 -0
  33. agentui/tailwind.config.ts +53 -0
  34. agentui/tsconfig.json +3 -0
  35. agentui/vite.config.ts +10 -0
  36. ai_parrot-0.17.2.dist-info/METADATA +472 -0
  37. ai_parrot-0.17.2.dist-info/RECORD +535 -0
  38. ai_parrot-0.17.2.dist-info/WHEEL +6 -0
  39. ai_parrot-0.17.2.dist-info/entry_points.txt +2 -0
  40. ai_parrot-0.17.2.dist-info/licenses/LICENSE +21 -0
  41. ai_parrot-0.17.2.dist-info/top_level.txt +6 -0
  42. crew-builder/.prettierrc +15 -0
  43. crew-builder/QUICKSTART.md +259 -0
  44. crew-builder/README.md +113 -0
  45. crew-builder/env.example +17 -0
  46. crew-builder/jsconfig.json +14 -0
  47. crew-builder/package-lock.json +4182 -0
  48. crew-builder/package.json +37 -0
  49. crew-builder/scripts/postinstall/apply-patches.mjs +260 -0
  50. crew-builder/src/app.css +62 -0
  51. crew-builder/src/app.d.ts +13 -0
  52. crew-builder/src/app.html +12 -0
  53. crew-builder/src/components/LoadingSpinner.svelte +64 -0
  54. crew-builder/src/components/ThemeSwitcher.svelte +149 -0
  55. crew-builder/src/components/index.js +9 -0
  56. crew-builder/src/lib/api/bots.ts +60 -0
  57. crew-builder/src/lib/api/chat.ts +80 -0
  58. crew-builder/src/lib/api/client.ts +56 -0
  59. crew-builder/src/lib/api/crew/crew.ts +136 -0
  60. crew-builder/src/lib/api/index.ts +5 -0
  61. crew-builder/src/lib/api/o365/auth.ts +65 -0
  62. crew-builder/src/lib/auth/auth.ts +54 -0
  63. crew-builder/src/lib/components/AgentNode.svelte +43 -0
  64. crew-builder/src/lib/components/BotCard.svelte +33 -0
  65. crew-builder/src/lib/components/ChatBubble.svelte +67 -0
  66. crew-builder/src/lib/components/ConfigPanel.svelte +278 -0
  67. crew-builder/src/lib/components/JsonTreeNode.svelte +76 -0
  68. crew-builder/src/lib/components/JsonViewer.svelte +24 -0
  69. crew-builder/src/lib/components/MarkdownEditor.svelte +48 -0
  70. crew-builder/src/lib/components/ThemeToggle.svelte +36 -0
  71. crew-builder/src/lib/components/Toast.svelte +67 -0
  72. crew-builder/src/lib/components/Toolbar.svelte +157 -0
  73. crew-builder/src/lib/components/index.ts +10 -0
  74. crew-builder/src/lib/config.ts +8 -0
  75. crew-builder/src/lib/stores/auth.svelte.ts +228 -0
  76. crew-builder/src/lib/stores/crewStore.ts +369 -0
  77. crew-builder/src/lib/stores/theme.svelte.js +145 -0
  78. crew-builder/src/lib/stores/toast.svelte.ts +69 -0
  79. crew-builder/src/lib/utils/conversation.ts +39 -0
  80. crew-builder/src/lib/utils/markdown.ts +122 -0
  81. crew-builder/src/lib/utils/talkHistory.ts +47 -0
  82. crew-builder/src/routes/+layout.svelte +20 -0
  83. crew-builder/src/routes/+page.svelte +539 -0
  84. crew-builder/src/routes/agents/+page.svelte +247 -0
  85. crew-builder/src/routes/agents/[agentId]/+page.svelte +288 -0
  86. crew-builder/src/routes/agents/[agentId]/+page.ts +7 -0
  87. crew-builder/src/routes/builder/+page.svelte +204 -0
  88. crew-builder/src/routes/crew/ask/+page.svelte +1052 -0
  89. crew-builder/src/routes/crew/ask/+page.ts +1 -0
  90. crew-builder/src/routes/integrations/o365/+page.svelte +304 -0
  91. crew-builder/src/routes/login/+page.svelte +197 -0
  92. crew-builder/src/routes/talk/[agentId]/+page.svelte +487 -0
  93. crew-builder/src/routes/talk/[agentId]/+page.ts +7 -0
  94. crew-builder/static/README.md +1 -0
  95. crew-builder/svelte.config.js +11 -0
  96. crew-builder/tailwind.config.ts +53 -0
  97. crew-builder/tsconfig.json +3 -0
  98. crew-builder/vite.config.ts +10 -0
  99. mcp_servers/calculator_server.py +309 -0
  100. parrot/__init__.py +27 -0
  101. parrot/__pycache__/__init__.cpython-310.pyc +0 -0
  102. parrot/__pycache__/version.cpython-310.pyc +0 -0
  103. parrot/_version.py +34 -0
  104. parrot/a2a/__init__.py +48 -0
  105. parrot/a2a/client.py +658 -0
  106. parrot/a2a/discovery.py +89 -0
  107. parrot/a2a/mixin.py +257 -0
  108. parrot/a2a/models.py +376 -0
  109. parrot/a2a/server.py +770 -0
  110. parrot/agents/__init__.py +29 -0
  111. parrot/bots/__init__.py +12 -0
  112. parrot/bots/a2a_agent.py +19 -0
  113. parrot/bots/abstract.py +3139 -0
  114. parrot/bots/agent.py +1129 -0
  115. parrot/bots/basic.py +9 -0
  116. parrot/bots/chatbot.py +669 -0
  117. parrot/bots/data.py +1618 -0
  118. parrot/bots/database/__init__.py +5 -0
  119. parrot/bots/database/abstract.py +3071 -0
  120. parrot/bots/database/cache.py +286 -0
  121. parrot/bots/database/models.py +468 -0
  122. parrot/bots/database/prompts.py +154 -0
  123. parrot/bots/database/retries.py +98 -0
  124. parrot/bots/database/router.py +269 -0
  125. parrot/bots/database/sql.py +41 -0
  126. parrot/bots/db/__init__.py +6 -0
  127. parrot/bots/db/abstract.py +556 -0
  128. parrot/bots/db/bigquery.py +602 -0
  129. parrot/bots/db/cache.py +85 -0
  130. parrot/bots/db/documentdb.py +668 -0
  131. parrot/bots/db/elastic.py +1014 -0
  132. parrot/bots/db/influx.py +898 -0
  133. parrot/bots/db/mock.py +96 -0
  134. parrot/bots/db/multi.py +783 -0
  135. parrot/bots/db/prompts.py +185 -0
  136. parrot/bots/db/sql.py +1255 -0
  137. parrot/bots/db/tools.py +212 -0
  138. parrot/bots/document.py +680 -0
  139. parrot/bots/hrbot.py +15 -0
  140. parrot/bots/kb.py +170 -0
  141. parrot/bots/mcp.py +36 -0
  142. parrot/bots/orchestration/README.md +463 -0
  143. parrot/bots/orchestration/__init__.py +1 -0
  144. parrot/bots/orchestration/agent.py +155 -0
  145. parrot/bots/orchestration/crew.py +3330 -0
  146. parrot/bots/orchestration/fsm.py +1179 -0
  147. parrot/bots/orchestration/hr.py +434 -0
  148. parrot/bots/orchestration/storage/__init__.py +4 -0
  149. parrot/bots/orchestration/storage/memory.py +100 -0
  150. parrot/bots/orchestration/storage/mixin.py +119 -0
  151. parrot/bots/orchestration/verify.py +202 -0
  152. parrot/bots/product.py +204 -0
  153. parrot/bots/prompts/__init__.py +96 -0
  154. parrot/bots/prompts/agents.py +155 -0
  155. parrot/bots/prompts/data.py +216 -0
  156. parrot/bots/prompts/output_generation.py +8 -0
  157. parrot/bots/scraper/__init__.py +3 -0
  158. parrot/bots/scraper/models.py +122 -0
  159. parrot/bots/scraper/scraper.py +1173 -0
  160. parrot/bots/scraper/templates.py +115 -0
  161. parrot/bots/stores/__init__.py +5 -0
  162. parrot/bots/stores/local.py +172 -0
  163. parrot/bots/webdev.py +81 -0
  164. parrot/cli.py +17 -0
  165. parrot/clients/__init__.py +16 -0
  166. parrot/clients/base.py +1491 -0
  167. parrot/clients/claude.py +1191 -0
  168. parrot/clients/factory.py +129 -0
  169. parrot/clients/google.py +4567 -0
  170. parrot/clients/gpt.py +1975 -0
  171. parrot/clients/grok.py +432 -0
  172. parrot/clients/groq.py +986 -0
  173. parrot/clients/hf.py +582 -0
  174. parrot/clients/models.py +18 -0
  175. parrot/conf.py +395 -0
  176. parrot/embeddings/__init__.py +9 -0
  177. parrot/embeddings/base.py +157 -0
  178. parrot/embeddings/google.py +98 -0
  179. parrot/embeddings/huggingface.py +74 -0
  180. parrot/embeddings/openai.py +84 -0
  181. parrot/embeddings/processor.py +88 -0
  182. parrot/exceptions.c +13868 -0
  183. parrot/exceptions.cpython-310-x86_64-linux-gnu.so +0 -0
  184. parrot/exceptions.pxd +22 -0
  185. parrot/exceptions.pxi +15 -0
  186. parrot/exceptions.pyx +44 -0
  187. parrot/generators/__init__.py +29 -0
  188. parrot/generators/base.py +200 -0
  189. parrot/generators/html.py +293 -0
  190. parrot/generators/react.py +205 -0
  191. parrot/generators/streamlit.py +203 -0
  192. parrot/generators/template.py +105 -0
  193. parrot/handlers/__init__.py +4 -0
  194. parrot/handlers/agent.py +861 -0
  195. parrot/handlers/agents/__init__.py +1 -0
  196. parrot/handlers/agents/abstract.py +900 -0
  197. parrot/handlers/bots.py +338 -0
  198. parrot/handlers/chat.py +915 -0
  199. parrot/handlers/creation.sql +192 -0
  200. parrot/handlers/crew/ARCHITECTURE.md +362 -0
  201. parrot/handlers/crew/README_BOTMANAGER_PERSISTENCE.md +303 -0
  202. parrot/handlers/crew/README_REDIS_PERSISTENCE.md +366 -0
  203. parrot/handlers/crew/__init__.py +0 -0
  204. parrot/handlers/crew/handler.py +801 -0
  205. parrot/handlers/crew/models.py +229 -0
  206. parrot/handlers/crew/redis_persistence.py +523 -0
  207. parrot/handlers/jobs/__init__.py +10 -0
  208. parrot/handlers/jobs/job.py +384 -0
  209. parrot/handlers/jobs/mixin.py +627 -0
  210. parrot/handlers/jobs/models.py +115 -0
  211. parrot/handlers/jobs/worker.py +31 -0
  212. parrot/handlers/models.py +596 -0
  213. parrot/handlers/o365_auth.py +105 -0
  214. parrot/handlers/stream.py +337 -0
  215. parrot/interfaces/__init__.py +6 -0
  216. parrot/interfaces/aws.py +143 -0
  217. parrot/interfaces/credentials.py +113 -0
  218. parrot/interfaces/database.py +27 -0
  219. parrot/interfaces/google.py +1123 -0
  220. parrot/interfaces/hierarchy.py +1227 -0
  221. parrot/interfaces/http.py +651 -0
  222. parrot/interfaces/images/__init__.py +0 -0
  223. parrot/interfaces/images/plugins/__init__.py +24 -0
  224. parrot/interfaces/images/plugins/abstract.py +58 -0
  225. parrot/interfaces/images/plugins/analisys.py +148 -0
  226. parrot/interfaces/images/plugins/classify.py +150 -0
  227. parrot/interfaces/images/plugins/classifybase.py +182 -0
  228. parrot/interfaces/images/plugins/detect.py +150 -0
  229. parrot/interfaces/images/plugins/exif.py +1103 -0
  230. parrot/interfaces/images/plugins/hash.py +52 -0
  231. parrot/interfaces/images/plugins/vision.py +104 -0
  232. parrot/interfaces/images/plugins/yolo.py +66 -0
  233. parrot/interfaces/images/plugins/zerodetect.py +197 -0
  234. parrot/interfaces/o365.py +978 -0
  235. parrot/interfaces/onedrive.py +822 -0
  236. parrot/interfaces/sharepoint.py +1435 -0
  237. parrot/interfaces/soap.py +257 -0
  238. parrot/loaders/__init__.py +8 -0
  239. parrot/loaders/abstract.py +1131 -0
  240. parrot/loaders/audio.py +199 -0
  241. parrot/loaders/basepdf.py +53 -0
  242. parrot/loaders/basevideo.py +1568 -0
  243. parrot/loaders/csv.py +409 -0
  244. parrot/loaders/docx.py +116 -0
  245. parrot/loaders/epubloader.py +316 -0
  246. parrot/loaders/excel.py +199 -0
  247. parrot/loaders/factory.py +55 -0
  248. parrot/loaders/files/__init__.py +0 -0
  249. parrot/loaders/files/abstract.py +39 -0
  250. parrot/loaders/files/html.py +26 -0
  251. parrot/loaders/files/text.py +63 -0
  252. parrot/loaders/html.py +152 -0
  253. parrot/loaders/markdown.py +442 -0
  254. parrot/loaders/pdf.py +373 -0
  255. parrot/loaders/pdfmark.py +320 -0
  256. parrot/loaders/pdftables.py +506 -0
  257. parrot/loaders/ppt.py +476 -0
  258. parrot/loaders/qa.py +63 -0
  259. parrot/loaders/splitters/__init__.py +10 -0
  260. parrot/loaders/splitters/base.py +138 -0
  261. parrot/loaders/splitters/md.py +228 -0
  262. parrot/loaders/splitters/token.py +143 -0
  263. parrot/loaders/txt.py +26 -0
  264. parrot/loaders/video.py +89 -0
  265. parrot/loaders/videolocal.py +218 -0
  266. parrot/loaders/videounderstanding.py +377 -0
  267. parrot/loaders/vimeo.py +167 -0
  268. parrot/loaders/web.py +599 -0
  269. parrot/loaders/youtube.py +504 -0
  270. parrot/manager/__init__.py +5 -0
  271. parrot/manager/manager.py +1030 -0
  272. parrot/mcp/__init__.py +28 -0
  273. parrot/mcp/adapter.py +105 -0
  274. parrot/mcp/cli.py +174 -0
  275. parrot/mcp/client.py +119 -0
  276. parrot/mcp/config.py +75 -0
  277. parrot/mcp/integration.py +842 -0
  278. parrot/mcp/oauth.py +933 -0
  279. parrot/mcp/server.py +225 -0
  280. parrot/mcp/transports/__init__.py +3 -0
  281. parrot/mcp/transports/base.py +279 -0
  282. parrot/mcp/transports/grpc_session.py +163 -0
  283. parrot/mcp/transports/http.py +312 -0
  284. parrot/mcp/transports/mcp.proto +108 -0
  285. parrot/mcp/transports/quic.py +1082 -0
  286. parrot/mcp/transports/sse.py +330 -0
  287. parrot/mcp/transports/stdio.py +309 -0
  288. parrot/mcp/transports/unix.py +395 -0
  289. parrot/mcp/transports/websocket.py +547 -0
  290. parrot/memory/__init__.py +16 -0
  291. parrot/memory/abstract.py +209 -0
  292. parrot/memory/agent.py +32 -0
  293. parrot/memory/cache.py +175 -0
  294. parrot/memory/core.py +555 -0
  295. parrot/memory/file.py +153 -0
  296. parrot/memory/mem.py +131 -0
  297. parrot/memory/redis.py +613 -0
  298. parrot/models/__init__.py +46 -0
  299. parrot/models/basic.py +118 -0
  300. parrot/models/compliance.py +208 -0
  301. parrot/models/crew.py +395 -0
  302. parrot/models/detections.py +654 -0
  303. parrot/models/generation.py +85 -0
  304. parrot/models/google.py +223 -0
  305. parrot/models/groq.py +23 -0
  306. parrot/models/openai.py +30 -0
  307. parrot/models/outputs.py +285 -0
  308. parrot/models/responses.py +938 -0
  309. parrot/notifications/__init__.py +743 -0
  310. parrot/openapi/__init__.py +3 -0
  311. parrot/openapi/components.yaml +641 -0
  312. parrot/openapi/config.py +322 -0
  313. parrot/outputs/__init__.py +32 -0
  314. parrot/outputs/formats/__init__.py +108 -0
  315. parrot/outputs/formats/altair.py +359 -0
  316. parrot/outputs/formats/application.py +122 -0
  317. parrot/outputs/formats/base.py +351 -0
  318. parrot/outputs/formats/bokeh.py +356 -0
  319. parrot/outputs/formats/card.py +424 -0
  320. parrot/outputs/formats/chart.py +436 -0
  321. parrot/outputs/formats/d3.py +255 -0
  322. parrot/outputs/formats/echarts.py +310 -0
  323. parrot/outputs/formats/generators/__init__.py +0 -0
  324. parrot/outputs/formats/generators/abstract.py +61 -0
  325. parrot/outputs/formats/generators/panel.py +145 -0
  326. parrot/outputs/formats/generators/streamlit.py +86 -0
  327. parrot/outputs/formats/generators/terminal.py +63 -0
  328. parrot/outputs/formats/holoviews.py +310 -0
  329. parrot/outputs/formats/html.py +147 -0
  330. parrot/outputs/formats/jinja2.py +46 -0
  331. parrot/outputs/formats/json.py +87 -0
  332. parrot/outputs/formats/map.py +933 -0
  333. parrot/outputs/formats/markdown.py +172 -0
  334. parrot/outputs/formats/matplotlib.py +237 -0
  335. parrot/outputs/formats/mixins/__init__.py +0 -0
  336. parrot/outputs/formats/mixins/emaps.py +855 -0
  337. parrot/outputs/formats/plotly.py +341 -0
  338. parrot/outputs/formats/seaborn.py +310 -0
  339. parrot/outputs/formats/table.py +397 -0
  340. parrot/outputs/formats/template_report.py +138 -0
  341. parrot/outputs/formats/yaml.py +125 -0
  342. parrot/outputs/formatter.py +152 -0
  343. parrot/outputs/templates/__init__.py +95 -0
  344. parrot/pipelines/__init__.py +0 -0
  345. parrot/pipelines/abstract.py +210 -0
  346. parrot/pipelines/detector.py +124 -0
  347. parrot/pipelines/models.py +90 -0
  348. parrot/pipelines/planogram.py +3002 -0
  349. parrot/pipelines/table.sql +97 -0
  350. parrot/plugins/__init__.py +106 -0
  351. parrot/plugins/importer.py +80 -0
  352. parrot/py.typed +0 -0
  353. parrot/registry/__init__.py +18 -0
  354. parrot/registry/registry.py +594 -0
  355. parrot/scheduler/__init__.py +1189 -0
  356. parrot/scheduler/models.py +60 -0
  357. parrot/security/__init__.py +16 -0
  358. parrot/security/prompt_injection.py +268 -0
  359. parrot/security/security_events.sql +25 -0
  360. parrot/services/__init__.py +1 -0
  361. parrot/services/mcp/__init__.py +8 -0
  362. parrot/services/mcp/config.py +13 -0
  363. parrot/services/mcp/server.py +295 -0
  364. parrot/services/o365_remote_auth.py +235 -0
  365. parrot/stores/__init__.py +7 -0
  366. parrot/stores/abstract.py +352 -0
  367. parrot/stores/arango.py +1090 -0
  368. parrot/stores/bigquery.py +1377 -0
  369. parrot/stores/cache.py +106 -0
  370. parrot/stores/empty.py +10 -0
  371. parrot/stores/faiss_store.py +1157 -0
  372. parrot/stores/kb/__init__.py +9 -0
  373. parrot/stores/kb/abstract.py +68 -0
  374. parrot/stores/kb/cache.py +165 -0
  375. parrot/stores/kb/doc.py +325 -0
  376. parrot/stores/kb/hierarchy.py +346 -0
  377. parrot/stores/kb/local.py +457 -0
  378. parrot/stores/kb/prompt.py +28 -0
  379. parrot/stores/kb/redis.py +659 -0
  380. parrot/stores/kb/store.py +115 -0
  381. parrot/stores/kb/user.py +374 -0
  382. parrot/stores/models.py +59 -0
  383. parrot/stores/pgvector.py +3 -0
  384. parrot/stores/postgres.py +2853 -0
  385. parrot/stores/utils/__init__.py +0 -0
  386. parrot/stores/utils/chunking.py +197 -0
  387. parrot/telemetry/__init__.py +3 -0
  388. parrot/telemetry/mixin.py +111 -0
  389. parrot/template/__init__.py +3 -0
  390. parrot/template/engine.py +259 -0
  391. parrot/tools/__init__.py +23 -0
  392. parrot/tools/abstract.py +644 -0
  393. parrot/tools/agent.py +363 -0
  394. parrot/tools/arangodbsearch.py +537 -0
  395. parrot/tools/arxiv_tool.py +188 -0
  396. parrot/tools/calculator/__init__.py +3 -0
  397. parrot/tools/calculator/operations/__init__.py +38 -0
  398. parrot/tools/calculator/operations/calculus.py +80 -0
  399. parrot/tools/calculator/operations/statistics.py +76 -0
  400. parrot/tools/calculator/tool.py +150 -0
  401. parrot/tools/cloudwatch.py +988 -0
  402. parrot/tools/codeinterpreter/__init__.py +127 -0
  403. parrot/tools/codeinterpreter/executor.py +371 -0
  404. parrot/tools/codeinterpreter/internals.py +473 -0
  405. parrot/tools/codeinterpreter/models.py +643 -0
  406. parrot/tools/codeinterpreter/prompts.py +224 -0
  407. parrot/tools/codeinterpreter/tool.py +664 -0
  408. parrot/tools/company_info/__init__.py +6 -0
  409. parrot/tools/company_info/tool.py +1138 -0
  410. parrot/tools/correlationanalysis.py +437 -0
  411. parrot/tools/database/abstract.py +286 -0
  412. parrot/tools/database/bq.py +115 -0
  413. parrot/tools/database/cache.py +284 -0
  414. parrot/tools/database/models.py +95 -0
  415. parrot/tools/database/pg.py +343 -0
  416. parrot/tools/databasequery.py +1159 -0
  417. parrot/tools/db.py +1800 -0
  418. parrot/tools/ddgo.py +370 -0
  419. parrot/tools/decorators.py +271 -0
  420. parrot/tools/dftohtml.py +282 -0
  421. parrot/tools/document.py +549 -0
  422. parrot/tools/ecs.py +819 -0
  423. parrot/tools/edareport.py +368 -0
  424. parrot/tools/elasticsearch.py +1049 -0
  425. parrot/tools/employees.py +462 -0
  426. parrot/tools/epson/__init__.py +96 -0
  427. parrot/tools/excel.py +683 -0
  428. parrot/tools/file/__init__.py +13 -0
  429. parrot/tools/file/abstract.py +76 -0
  430. parrot/tools/file/gcs.py +378 -0
  431. parrot/tools/file/local.py +284 -0
  432. parrot/tools/file/s3.py +511 -0
  433. parrot/tools/file/tmp.py +309 -0
  434. parrot/tools/file/tool.py +501 -0
  435. parrot/tools/file_reader.py +129 -0
  436. parrot/tools/flowtask/__init__.py +19 -0
  437. parrot/tools/flowtask/tool.py +761 -0
  438. parrot/tools/gittoolkit.py +508 -0
  439. parrot/tools/google/__init__.py +18 -0
  440. parrot/tools/google/base.py +169 -0
  441. parrot/tools/google/tools.py +1251 -0
  442. parrot/tools/googlelocation.py +5 -0
  443. parrot/tools/googleroutes.py +5 -0
  444. parrot/tools/googlesearch.py +5 -0
  445. parrot/tools/googlesitesearch.py +5 -0
  446. parrot/tools/googlevoice.py +2 -0
  447. parrot/tools/gvoice.py +695 -0
  448. parrot/tools/ibisworld/README.md +225 -0
  449. parrot/tools/ibisworld/__init__.py +11 -0
  450. parrot/tools/ibisworld/tool.py +366 -0
  451. parrot/tools/jiratoolkit.py +1718 -0
  452. parrot/tools/manager.py +1098 -0
  453. parrot/tools/math.py +152 -0
  454. parrot/tools/metadata.py +476 -0
  455. parrot/tools/msteams.py +1621 -0
  456. parrot/tools/msword.py +635 -0
  457. parrot/tools/multidb.py +580 -0
  458. parrot/tools/multistoresearch.py +369 -0
  459. parrot/tools/networkninja.py +167 -0
  460. parrot/tools/nextstop/__init__.py +4 -0
  461. parrot/tools/nextstop/base.py +286 -0
  462. parrot/tools/nextstop/employee.py +733 -0
  463. parrot/tools/nextstop/store.py +462 -0
  464. parrot/tools/notification.py +435 -0
  465. parrot/tools/o365/__init__.py +42 -0
  466. parrot/tools/o365/base.py +295 -0
  467. parrot/tools/o365/bundle.py +522 -0
  468. parrot/tools/o365/events.py +554 -0
  469. parrot/tools/o365/mail.py +992 -0
  470. parrot/tools/o365/onedrive.py +497 -0
  471. parrot/tools/o365/sharepoint.py +641 -0
  472. parrot/tools/openapi_toolkit.py +904 -0
  473. parrot/tools/openweather.py +527 -0
  474. parrot/tools/pdfprint.py +1001 -0
  475. parrot/tools/powerbi.py +518 -0
  476. parrot/tools/powerpoint.py +1113 -0
  477. parrot/tools/pricestool.py +146 -0
  478. parrot/tools/products/__init__.py +246 -0
  479. parrot/tools/prophet_tool.py +171 -0
  480. parrot/tools/pythonpandas.py +630 -0
  481. parrot/tools/pythonrepl.py +910 -0
  482. parrot/tools/qsource.py +436 -0
  483. parrot/tools/querytoolkit.py +395 -0
  484. parrot/tools/quickeda.py +827 -0
  485. parrot/tools/resttool.py +553 -0
  486. parrot/tools/retail/__init__.py +0 -0
  487. parrot/tools/retail/bby.py +528 -0
  488. parrot/tools/sandboxtool.py +703 -0
  489. parrot/tools/sassie/__init__.py +352 -0
  490. parrot/tools/scraping/__init__.py +7 -0
  491. parrot/tools/scraping/docs/select.md +466 -0
  492. parrot/tools/scraping/documentation.md +1278 -0
  493. parrot/tools/scraping/driver.py +436 -0
  494. parrot/tools/scraping/models.py +576 -0
  495. parrot/tools/scraping/options.py +85 -0
  496. parrot/tools/scraping/orchestrator.py +517 -0
  497. parrot/tools/scraping/readme.md +740 -0
  498. parrot/tools/scraping/tool.py +3115 -0
  499. parrot/tools/seasonaldetection.py +642 -0
  500. parrot/tools/shell_tool/__init__.py +5 -0
  501. parrot/tools/shell_tool/actions.py +408 -0
  502. parrot/tools/shell_tool/engine.py +155 -0
  503. parrot/tools/shell_tool/models.py +322 -0
  504. parrot/tools/shell_tool/tool.py +442 -0
  505. parrot/tools/site_search.py +214 -0
  506. parrot/tools/textfile.py +418 -0
  507. parrot/tools/think.py +378 -0
  508. parrot/tools/toolkit.py +298 -0
  509. parrot/tools/webapp_tool.py +187 -0
  510. parrot/tools/whatif.py +1279 -0
  511. parrot/tools/workday/MULTI_WSDL_EXAMPLE.md +249 -0
  512. parrot/tools/workday/__init__.py +6 -0
  513. parrot/tools/workday/models.py +1389 -0
  514. parrot/tools/workday/tool.py +1293 -0
  515. parrot/tools/yfinance_tool.py +306 -0
  516. parrot/tools/zipcode.py +217 -0
  517. parrot/utils/__init__.py +2 -0
  518. parrot/utils/helpers.py +73 -0
  519. parrot/utils/parsers/__init__.py +5 -0
  520. parrot/utils/parsers/toml.c +12078 -0
  521. parrot/utils/parsers/toml.cpython-310-x86_64-linux-gnu.so +0 -0
  522. parrot/utils/parsers/toml.pyx +21 -0
  523. parrot/utils/toml.py +11 -0
  524. parrot/utils/types.cpp +20936 -0
  525. parrot/utils/types.cpython-310-x86_64-linux-gnu.so +0 -0
  526. parrot/utils/types.pyx +213 -0
  527. parrot/utils/uv.py +11 -0
  528. parrot/version.py +10 -0
  529. parrot/yaml-rs/Cargo.lock +350 -0
  530. parrot/yaml-rs/Cargo.toml +19 -0
  531. parrot/yaml-rs/pyproject.toml +19 -0
  532. parrot/yaml-rs/python/yaml_rs/__init__.py +81 -0
  533. parrot/yaml-rs/src/lib.rs +222 -0
  534. requirements/docker-compose.yml +24 -0
  535. requirements/requirements-dev.txt +21 -0
@@ -0,0 +1,60 @@
1
+ from typing import Any, Dict, Optional, Callable, List
2
+ from datetime import datetime
3
+ import uuid
4
+ from asyncdb.models import Model, Field
5
+
6
+
7
+ class AgentSchedule(Model):
8
+ """
9
+ Database model for storing agent schedules.
10
+
11
+ SQL Table Creation:
12
+ CREATE TABLE IF NOT EXISTS navigator.agents_scheduler (
13
+ schedule_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
14
+ agent_id VARCHAR NOT NULL,
15
+ agent_name VARCHAR NOT NULL,
16
+ prompt TEXT,
17
+ method_name VARCHAR,
18
+ schedule_type VARCHAR NOT NULL,
19
+ schedule_config JSONB NOT NULL,
20
+ enabled BOOLEAN DEFAULT TRUE,
21
+ created_by INTEGER,
22
+ created_email VARCHAR,
23
+ created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
24
+ updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
25
+ last_run TIMESTAMP WITH TIME ZONE,
26
+ next_run TIMESTAMP WITH TIME ZONE,
27
+ run_count INTEGER DEFAULT 0,
28
+ metadata JSONB DEFAULT '{}'::JSONB,
29
+ is_crew BOOLEAN DEFAULT FALSE,
30
+ send_result JSONB DEFAULT '{}'::JSONB
31
+ );
32
+
33
+ CREATE INDEX idx_agents_scheduler_enabled ON navigator.agents_scheduler(enabled);
34
+ CREATE INDEX idx_agents_scheduler_agent ON navigator.agents_scheduler(agent_name);
35
+ """
36
+ schedule_id: uuid.UUID = Field(primary_key=True, default_factory=uuid.uuid4)
37
+ agent_id: str = Field(required=True)
38
+ agent_name: str = Field(required=True)
39
+ prompt: Optional[str] = Field(required=False)
40
+ method_name: Optional[str] = Field(required=False)
41
+ schedule_type: str = Field(required=True)
42
+ schedule_config: dict = Field(required=True, default_factory=dict)
43
+ enabled: bool = Field(required=False, default=True)
44
+ created_by: Optional[int] = Field(required=False)
45
+ created_email: Optional[str] = Field(required=False)
46
+ created_at: datetime = Field(required=False, default_factory=datetime.now)
47
+ updated_at: datetime = Field(required=False, default_factory=datetime.now)
48
+ last_run: Optional[datetime] = Field(required=False)
49
+ next_run: Optional[datetime] = Field(required=False)
50
+ run_count: int = Field(required=False, default=0)
51
+ metadata: dict = Field(required=False, default_factory=dict)
52
+ is_crew: bool = Field(required=False, default=False)
53
+ send_result: dict = Field(required=False, default_factory=dict)
54
+
55
+ class Meta:
56
+ driver = 'pg'
57
+ name = "agents_scheduler"
58
+ schema = "navigator"
59
+ strict = True
60
+ frozen = False
@@ -0,0 +1,16 @@
1
+ """
2
+ Security utilities for AI-Parrot.
3
+ """
4
+ from .prompt_injection import (
5
+ PromptInjectionDetector,
6
+ SecurityEventLogger,
7
+ ThreatLevel,
8
+ PromptInjectionException
9
+ )
10
+
11
+ __all__ = [
12
+ 'PromptInjectionDetector',
13
+ 'SecurityEventLogger',
14
+ 'ThreatLevel',
15
+ 'PromptInjectionException'
16
+ ]
@@ -0,0 +1,268 @@
1
+ """
2
+ Prompt Injection Detection and Protection.
3
+ """
4
+ from typing import Optional, Dict, Any, List, Tuple
5
+ from enum import Enum
6
+ import re
7
+ from datetime import datetime, timezone
8
+ from navconfig.logging import logging
9
+
10
+
11
+ class ThreatLevel(Enum):
12
+ """Severity levels for detected threats."""
13
+ LOW = "low"
14
+ MEDIUM = "medium"
15
+ HIGH = "high"
16
+ CRITICAL = "critical"
17
+
18
+
19
+ class PromptInjectionException(Exception):
20
+ """Raised when a critical prompt injection is detected in strict mode."""
21
+ def __init__(self, message: str, threats: List[Dict], original_input: str):
22
+ super().__init__(message)
23
+ self.threats = threats
24
+ self.original_input = original_input
25
+
26
+
27
+ class PromptInjectionDetector:
28
+ """
29
+ Detects and mitigates prompt injection attempts in user questions.
30
+ """
31
+
32
+ # CRITICAL: Direct instruction override attempts
33
+ CRITICAL_PATTERNS = [
34
+ (
35
+ re.compile(r'ignore\s+(all\s+)?(previous|above|prior)\s+instructions?', re.IGNORECASE),
36
+ "Direct instruction override"
37
+ ),
38
+ (
39
+ re.compile(r'forget\s+(everything|all|your\s+(instructions?|rules?|prompt))', re.IGNORECASE),
40
+ "Memory wipe attempt"
41
+ ),
42
+ (
43
+ re.compile(r'you\s+are\s+now\s+(a|an|no\s+longer)', re.IGNORECASE),
44
+ "Role hijacking"
45
+ ),
46
+ (
47
+ re.compile(r'disregard\s+(all\s+)?(previous|above|prior|your)', re.IGNORECASE),
48
+ "Disregard command"
49
+ ),
50
+ ]
51
+
52
+ # HIGH: System impersonation
53
+ HIGH_PATTERNS = [
54
+ (
55
+ re.compile(r'system\s*:\s*', re.IGNORECASE),
56
+ "System role impersonation"
57
+ ),
58
+ (
59
+ re.compile(r'<\s*/?system\s*>', re.IGNORECASE),
60
+ "System tag injection"
61
+ ),
62
+ (
63
+ re.compile(r'\[SYSTEM\]|\(SYSTEM\)|【SYSTEM】', re.IGNORECASE),
64
+ "System marker injection"
65
+ ),
66
+ (
67
+ re.compile(r'new\s+instructions?:\s*', re.IGNORECASE),
68
+ "Instruction replacement"
69
+ ),
70
+ (
71
+ re.compile(r'assistant\s*:\s*ignore', re.IGNORECASE),
72
+ "Assistant role injection"
73
+ ),
74
+ ]
75
+
76
+ # MEDIUM: Prompt extraction attempts
77
+ MEDIUM_PATTERNS = [
78
+ (
79
+ re.compile(r'(reveal|show|print|display|output)\s+(your\s+)?(prompt|instructions|system\s+message)', re.IGNORECASE),
80
+ "Prompt extraction attempt"
81
+ ),
82
+ (
83
+ re.compile(r'what\s+(is|are)\s+your\s+(original\s+)?(instructions?|prompt|rules?)', re.IGNORECASE),
84
+ "Instruction disclosure request"
85
+ ),
86
+ ]
87
+
88
+ def __init__(self, logger: Optional[logging.Logger] = None):
89
+ self.logger = logger or logging.getLogger(__name__)
90
+
91
+ def detect_threats(self, text: str) -> List[Dict[str, Any]]:
92
+ """
93
+ Scan text for prompt injection patterns.
94
+
95
+ Returns:
96
+ List of detected threats with details
97
+ """
98
+ if not text or not isinstance(text, str):
99
+ return []
100
+
101
+ threats = []
102
+
103
+ # Check CRITICAL patterns
104
+ for pattern, description in self.CRITICAL_PATTERNS:
105
+ if match := pattern.search(text):
106
+ threats.append({
107
+ 'level': ThreatLevel.CRITICAL,
108
+ 'pattern': pattern.pattern,
109
+ 'description': description,
110
+ 'matched_text': match.group(0),
111
+ 'position': match.span()
112
+ })
113
+
114
+ # Check HIGH patterns
115
+ for pattern, description in self.HIGH_PATTERNS:
116
+ if match := pattern.search(text):
117
+ threats.append({
118
+ 'level': ThreatLevel.HIGH,
119
+ 'pattern': pattern.pattern,
120
+ 'description': description,
121
+ 'matched_text': match.group(0),
122
+ 'position': match.span()
123
+ })
124
+
125
+ # Check MEDIUM patterns
126
+ for pattern, description in self.MEDIUM_PATTERNS:
127
+ if match := pattern.search(text):
128
+ threats.append({
129
+ 'level': ThreatLevel.MEDIUM,
130
+ 'pattern': pattern.pattern,
131
+ 'description': description,
132
+ 'matched_text': match.group(0),
133
+ 'position': match.span()
134
+ })
135
+
136
+ return threats
137
+
138
+ def sanitize(
139
+ self,
140
+ text: str,
141
+ strict: bool = True,
142
+ replacement: str = "[FILTERED_CONTENT]"
143
+ ) -> Tuple[str, List[Dict[str, Any]]]:
144
+ """
145
+ Sanitize text by replacing detected patterns.
146
+
147
+ Args:
148
+ text: Input text to sanitize
149
+ strict: If True, replace patterns; if False, only detect
150
+ replacement: Text to use for replacements
151
+
152
+ Returns:
153
+ Tuple of (sanitized_text, detected_threats)
154
+ """
155
+ threats = self.detect_threats(text)
156
+
157
+ if not strict or not threats:
158
+ return text, threats
159
+
160
+ sanitized = text
161
+
162
+ # In strict mode, replace CRITICAL and HIGH patterns
163
+ for pattern, _ in self.CRITICAL_PATTERNS + self.HIGH_PATTERNS:
164
+ sanitized = pattern.sub(replacement, sanitized)
165
+
166
+ return sanitized, threats
167
+
168
+
169
+ class SecurityEventLogger:
170
+ """
171
+ Logs security events with session tracking.
172
+ """
173
+
174
+ def __init__(self, db_pool=None, logger: Optional[logging.Logger] = None):
175
+ self.db_pool = db_pool
176
+ self.logger = logger or logging.getLogger(__name__)
177
+
178
+ async def log_injection_attempt(
179
+ self,
180
+ user_id: str,
181
+ session_id: str,
182
+ chatbot_id: str,
183
+ threats: List[Dict[str, Any]],
184
+ original_input: str,
185
+ sanitized_input: str,
186
+ metadata: Optional[Dict[str, Any]] = None
187
+ ):
188
+ """
189
+ Log a detected prompt injection attempt.
190
+ """
191
+ max_severity = max(
192
+ (t['level'] for t in threats),
193
+ default=ThreatLevel.LOW
194
+ )
195
+
196
+ # Always log to application logger
197
+ for threat in threats:
198
+ self.logger.warning(
199
+ f"🔒 SECURITY: Prompt injection detected | "
200
+ f"Severity: {threat['level'].value.upper()} | "
201
+ f"User: {user_id} | Session: {session_id} | "
202
+ f"Bot: {chatbot_id} | "
203
+ f"Type: {threat['description']} | "
204
+ f"Pattern: '{threat['matched_text']}'"
205
+ )
206
+
207
+ # Log to database if pool available
208
+ if self.db_pool:
209
+ await self._log_to_database(
210
+ user_id=user_id,
211
+ session_id=session_id,
212
+ chatbot_id=chatbot_id,
213
+ threats=threats,
214
+ original_input=original_input,
215
+ sanitized_input=sanitized_input,
216
+ max_severity=max_severity,
217
+ metadata=metadata or {}
218
+ )
219
+
220
+ async def _log_to_database(
221
+ self,
222
+ user_id: str,
223
+ session_id: str,
224
+ chatbot_id: str,
225
+ threats: List[Dict[str, Any]],
226
+ original_input: str,
227
+ sanitized_input: str,
228
+ max_severity: ThreatLevel,
229
+ metadata: Dict[str, Any]
230
+ ):
231
+ """Store security event in database."""
232
+ try:
233
+ query = """
234
+ INSERT INTO navigator.security_events (
235
+ event_type,
236
+ user_id,
237
+ session_id,
238
+ chatbot_id,
239
+ severity,
240
+ threat_details,
241
+ original_input,
242
+ sanitized_input,
243
+ metadata,
244
+ created_at
245
+ ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
246
+ """
247
+
248
+ async with await self.db_pool.acquire() as conn:
249
+ await conn.execute(
250
+ query,
251
+ 'prompt_injection',
252
+ user_id,
253
+ session_id,
254
+ chatbot_id,
255
+ max_severity.value,
256
+ threats, # PostgreSQL JSONB
257
+ original_input[:2000], # Truncate for storage
258
+ sanitized_input[:2000],
259
+ metadata,
260
+ datetime.now(timezone.utc),
261
+ )
262
+
263
+ self.logger.info(
264
+ f"Security event logged to database for user {user_id}"
265
+ )
266
+
267
+ except Exception as e:
268
+ self.logger.error(f"Failed to log security event to database: {e}")
@@ -0,0 +1,25 @@
1
+ -- Security events tracking table
2
+ CREATE TABLE IF NOT EXISTS security_events (
3
+ event_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
4
+ event_type VARCHAR(50) NOT NULL,
5
+ user_id VARCHAR(255) NOT NULL,
6
+ session_id VARCHAR(255) NOT NULL,
7
+ chatbot_id VARCHAR(255),
8
+ severity VARCHAR(20) NOT NULL,
9
+ threat_details JSONB NOT NULL,
10
+ original_input TEXT,
11
+ sanitized_input TEXT,
12
+ metadata JSONB DEFAULT '{}',
13
+ created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
14
+ );
15
+
16
+ -- Indexes for security monitoring
17
+ CREATE INDEX idx_security_events_user_id ON security_events(user_id);
18
+ CREATE INDEX idx_security_events_session_id ON security_events(session_id);
19
+ CREATE INDEX idx_security_events_severity ON security_events(severity);
20
+ CREATE INDEX idx_security_events_created_at ON security_events(created_at DESC);
21
+ CREATE INDEX idx_security_events_chatbot ON security_events(chatbot_id);
22
+
23
+ -- Index for monitoring critical/high severity events
24
+ CREATE INDEX idx_security_events_high_severity ON security_events(severity, created_at DESC)
25
+ WHERE severity IN ('critical', 'high');
@@ -0,0 +1 @@
1
+ """Parrot service helpers."""
@@ -0,0 +1,8 @@
1
+ from .config import TransportConfig
2
+ from .server import ParrotMCPServer # noqa: F401
3
+
4
+
5
+ __all__ = (
6
+ "ParrotMCPServer",
7
+ "TransportConfig",
8
+ )
@@ -0,0 +1,13 @@
1
+ from typing import Optional
2
+ from dataclasses import dataclass
3
+
4
+ @dataclass
5
+ class TransportConfig:
6
+ """Configuration for a single transport."""
7
+ transport: str # "stdio" or "http" or "sse" or "unix"
8
+ enabled: bool = True
9
+ host: Optional[str] = None # Only for HTTP
10
+ port: Optional[int] = None # Only for HTTP
11
+ url: Optional[str] = None # Only for HTTP/SSE transport
12
+ name_suffix: Optional[str] = None # e.g., "local" or "remote"
13
+ socket_path: Optional[str] = None # Only for UNIX socket transport
@@ -0,0 +1,295 @@
1
+ """Utilities for starting the MCP server inside the aiohttp application."""
2
+ from __future__ import annotations
3
+ from typing import Dict, List, Optional, Union
4
+ import asyncio
5
+ import contextlib
6
+ import inspect
7
+ from importlib import import_module
8
+ from aiohttp import web
9
+ from navconfig.logging import logging
10
+ from ...conf import (
11
+ MCP_SERVER_DESCRIPTION,
12
+ MCP_SERVER_HOST,
13
+ MCP_SERVER_LOG_LEVEL,
14
+ MCP_SERVER_NAME,
15
+ MCP_SERVER_PORT,
16
+ MCP_SERVER_TRANSPORT,
17
+ MCP_STARTED_TOOLS,
18
+ )
19
+ from ...mcp.server import MCPServer, MCPServerConfig, HttpMCPServer, SseMCPServer
20
+ from ...tools.abstract import AbstractTool
21
+ from ...tools.toolkit import AbstractToolkit
22
+ from .config import TransportConfig
23
+
24
+
25
+ class ParrotMCPServer:
26
+ """Manage lifecycle of multiple MCP servers (multi-transport) attached to an aiohttp app."""
27
+
28
+ def __init__(
29
+ self,
30
+ *,
31
+ transports: Optional[Union[str, List[str], Dict[str, TransportConfig]]] = None,
32
+ host: Optional[str] = None,
33
+ port: Optional[int] = None,
34
+ tools: Optional[Dict[str, str]] = None,
35
+ name: Optional[str] = None,
36
+ description: Optional[str] = None,
37
+ log_level: Optional[str] = None,
38
+ allow_anonymous: bool = True,
39
+ ) -> None:
40
+ self.name = name or MCP_SERVER_NAME
41
+ self.description = description or MCP_SERVER_DESCRIPTION
42
+ self.log_level = log_level or MCP_SERVER_LOG_LEVEL
43
+ self.tools_config = tools if isinstance(tools, dict) else MCP_STARTED_TOOLS
44
+ self.toolkit_instance = tools if isinstance(tools, AbstractToolkit) else None
45
+ self.allow_anonymous = allow_anonymous
46
+ # Parse transport configuration
47
+ self.transport_configs = self._parse_transports(
48
+ transports, host, port
49
+ )
50
+ # Multiple servers, one per transport
51
+ self.servers: Dict[str, MCPServer] = {}
52
+ self._server_tasks: Dict[str, asyncio.Task] = {}
53
+ self.logger = logging.getLogger("Parrot.MCPServer")
54
+ self.app: Optional[web.Application] = None
55
+
56
+ # Multiple servers, one per transport
57
+ self.servers: Dict[str, MCPServer] = {}
58
+ self._server_tasks: Dict[str, asyncio.Task] = {}
59
+
60
+ def _parse_transports(
61
+ self,
62
+ transports: Optional[Union[str, List[str], Dict[str, TransportConfig]]],
63
+ default_host: Optional[str],
64
+ default_port: Optional[int],
65
+ ) -> Dict[str, TransportConfig]:
66
+ """Parse transport configuration into normalized format."""
67
+ if transports is None:
68
+ # Default: HTTP only
69
+ transports = MCP_SERVER_TRANSPORT or "http"
70
+
71
+ configs = {}
72
+
73
+ if isinstance(transports, str):
74
+ # Single transport string: "stdio" or "http"
75
+ transport = transports.lower()
76
+ host = default_host or MCP_SERVER_HOST if transport in {"http", "sse"} else None
77
+ port = default_port or MCP_SERVER_PORT if transport in {"http", "sse"} else None
78
+ configs[transport] = TransportConfig(
79
+ transport=transport,
80
+ host=host,
81
+ port=port,
82
+ )
83
+
84
+ elif isinstance(transports, list):
85
+ # List of transport names: ["stdio", "http"]
86
+ for i, transport in enumerate(transports):
87
+ transport = transport.lower()
88
+ is_http_like = transport in {"http", "sse"}
89
+ port = ((default_port or MCP_SERVER_PORT) + i) if is_http_like else None
90
+ configs[transport] = TransportConfig(
91
+ transport=transport,
92
+ host=(default_host or MCP_SERVER_HOST) if is_http_like else None,
93
+ port=port,
94
+ name_suffix=f"{i}" if len(transports) > 1 else None,
95
+ )
96
+
97
+ elif isinstance(transports, dict):
98
+ # Full control: {"stdio": TransportConfig(...), "http": TransportConfig(...)}
99
+ configs = transports
100
+
101
+ return configs
102
+
103
+ def setup(self, app: web.Application) -> None:
104
+ """Register lifecycle hooks inside the aiohttp application."""
105
+ self.app = app
106
+ app["parrot_mcp_server"] = self
107
+ app.on_startup.append(self.on_startup)
108
+ app.on_shutdown.append(self.on_shutdown)
109
+ self.logger.notice(
110
+ ":: ParrotMCPServer registered for aiohttp app lifecycle management"
111
+ )
112
+
113
+ async def on_startup(self, app: web.Application) -> None: # pylint: disable=unused-argument
114
+ """Start the MCP server once aiohttp finishes bootstrapping."""
115
+ tools = await self._load_configured_tools()
116
+ if not tools:
117
+ self.logger.info("No MCP tools configured to start")
118
+ return
119
+
120
+ # Start a server for each enabled transport
121
+ for transport_key, config in self.transport_configs.items():
122
+ if not config.enabled:
123
+ continue
124
+
125
+ server_name = self.name
126
+ if config.name_suffix:
127
+ server_name = f"{self.name}-{config.name_suffix}"
128
+
129
+ mcp_config = MCPServerConfig(
130
+ name=server_name,
131
+ description=self.description,
132
+ transport=config.transport,
133
+ host=config.host,
134
+ port=config.port,
135
+ log_level=self.log_level,
136
+ )
137
+
138
+ # Ensure MCP endpoints bypass the auth middleware (uses AuthHandler.exclude_list)
139
+ self._add_auth_exclusions(
140
+ [
141
+ mcp_config.base_path,
142
+ f"{mcp_config.base_path.rstrip('/')}/events",
143
+ f"{mcp_config.base_path.rstrip('/')}/.well-known/oauth-authorization-server",
144
+ f"{mcp_config.base_path.rstrip('/')}/oauth/register",
145
+ f"{mcp_config.base_path.rstrip('/')}/oauth/authorize",
146
+ f"{mcp_config.base_path.rstrip('/')}/oauth/token",
147
+ ]
148
+ )
149
+
150
+ if config.transport == "stdio":
151
+ server = MCPServer(mcp_config)
152
+ server.register_tools(tools)
153
+ self.servers[transport_key] = server
154
+
155
+ start_coro = server.start()
156
+ self._server_tasks[transport_key] = asyncio.create_task(start_coro)
157
+ self.logger.info(f"Spawned stdio MCP server task: {server_name}")
158
+
159
+ elif config.transport in {"http", "sse"}:
160
+ # Launch HTTP/SSE MCP server using existing aiohttp app
161
+ if config.transport == "sse":
162
+ server = SseMCPServer(mcp_config, parent_app=app)
163
+ else:
164
+ server = HttpMCPServer(mcp_config, parent_app=app)
165
+ server.register_tools(tools)
166
+ self.servers[transport_key] = server
167
+
168
+ # Solo registra rutas, no crea nueva app
169
+ await server.start()
170
+
171
+ self.logger.info(
172
+ f"MCP server '{server_name}' routes registered on existing app"
173
+ )
174
+
175
+ async def on_shutdown(self, app: web.Application) -> None: # pylint: disable=unused-argument
176
+ """Stop the MCP server when aiohttp starts shutting down."""
177
+ shutdown_errors = []
178
+
179
+ # Stop all servers
180
+ for transport_key, server in self.servers.items():
181
+ try:
182
+ await server.stop()
183
+ self.logger.info(f"Stopped MCP server: {transport_key}")
184
+ except Exception as exc:
185
+ shutdown_errors.append((transport_key, exc))
186
+ self.logger.error(f"Failed stopping MCP server {transport_key}: {exc}")
187
+
188
+ # Cancel stdio tasks
189
+ for transport_key, task in self._server_tasks.items():
190
+ if not task.done():
191
+ task.cancel()
192
+ with contextlib.suppress(asyncio.CancelledError):
193
+ await task
194
+
195
+ self._server_tasks.clear()
196
+ self.servers.clear()
197
+
198
+ if shutdown_errors:
199
+ self.logger.warning(
200
+ f"Shutdown completed with {len(shutdown_errors)} errors"
201
+ )
202
+ else:
203
+ self.logger.info("All MCP servers shutdown complete")
204
+
205
+ def _add_auth_exclusions(self, paths: List[str]) -> None:
206
+ """Register paths that should bypass the Auth middleware (if present)."""
207
+ if not self.allow_anonymous:
208
+ return
209
+
210
+ if not self.app:
211
+ return
212
+
213
+ auth_handler = self.app.get("auth")
214
+ if not auth_handler:
215
+ self.logger.debug("Auth handler not found; skipping auth exclusions for MCP")
216
+ return
217
+
218
+ for path in paths:
219
+ auth_handler.add_exclude_list(path)
220
+ # Also add glob-style pattern to cover subpaths
221
+ if not path.endswith("*"):
222
+ auth_handler.add_exclude_list(f"{path}*")
223
+ self.logger.info("Registered MCP paths as auth exclusions: %s", paths)
224
+
225
+ async def _load_configured_tools(self) -> List[AbstractTool]:
226
+ """Instantiate every tool declared in configuration."""
227
+ loaded: List[AbstractTool] = []
228
+
229
+ if not self.tools_config:
230
+ return loaded
231
+
232
+ for class_name, module_path in self.tools_config.items():
233
+ if isinstance(module_path, (AbstractTool)):
234
+ loaded.append(module_path)
235
+ continue
236
+ elif isinstance(module_path, AbstractToolkit):
237
+ toolkit = module_path
238
+ await self._maybe_start_toolkit(toolkit, class_name)
239
+ loaded.extend(toolkit.get_tools())
240
+ continue
241
+ try:
242
+ module = import_module(module_path)
243
+ tool_cls = getattr(module, class_name)
244
+ except (ImportError, AttributeError) as exc:
245
+ self.logger.error(
246
+ "Unable to import MCP tool %s from %s: %s",
247
+ class_name,
248
+ module_path,
249
+ exc,
250
+ )
251
+ continue
252
+
253
+ instances = await self._initialize_tool(tool_cls, class_name)
254
+ if not instances:
255
+ continue
256
+
257
+ loaded.extend(instances)
258
+
259
+ self.logger.info("Loaded %s MCP tools", len(loaded))
260
+ return loaded
261
+
262
+ async def _initialize_tool(
263
+ self,
264
+ tool_cls,
265
+ class_name: str,
266
+ ) -> List[AbstractTool]:
267
+ """Instantiate either a toolkit or an individual AbstractTool."""
268
+ try:
269
+ instance = tool_cls()
270
+ except Exception as exc:
271
+ self.logger.error("Unable to instantiate %s: %s", class_name, exc)
272
+ return []
273
+
274
+ if isinstance(instance, AbstractToolkit):
275
+ await self._maybe_start_toolkit(instance, class_name)
276
+ return list(instance.get_tools())
277
+
278
+ if isinstance(instance, AbstractTool):
279
+ return [instance]
280
+
281
+ self.logger.warning(
282
+ "Configured MCP entry %s is neither an AbstractTool nor AbstractToolkit",
283
+ class_name,
284
+ )
285
+ return []
286
+
287
+ async def _maybe_start_toolkit(self, toolkit: AbstractToolkit, class_name: str) -> None:
288
+ """Call toolkit.start() when available."""
289
+ try:
290
+ result = toolkit.start()
291
+ if inspect.isawaitable(result):
292
+ await result
293
+ self.logger.debug("Toolkit %s started", class_name)
294
+ except Exception as exc: # pragma: no cover - logging path
295
+ self.logger.error("Toolkit %s failed during startup: %s", class_name, exc)