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,330 @@
1
+ import asyncio
2
+ import json
3
+ import logging
4
+ import uuid
5
+ import contextlib
6
+ from typing import Dict, Any, Optional
7
+ from aiohttp import web
8
+ import aiohttp
9
+
10
+ from parrot.mcp.config import MCPServerConfig
11
+ from parrot.mcp.transports.base import MCPServerBase
12
+ from parrot.mcp.oauth import OAuthRoutesMixin
13
+ from parrot.mcp.client import MCPClientConfig, MCPConnectionError
14
+ from parrot.mcp.transports.http import HttpMCPSession
15
+ from aiohttp_sse_client import client as sse_client
16
+
17
+ class SseMCPServer(OAuthRoutesMixin, MCPServerBase):
18
+ """MCP server using SSE transport compatible with ChatGPT and OpenAI MCP clients."""
19
+
20
+ def __init__(self, config: MCPServerConfig, parent_app: Optional[web.Application] = None):
21
+ super().__init__(config)
22
+ self.app = parent_app or web.Application()
23
+ self.base_path = config.base_path or "/mcp"
24
+ self.events_path = config.events_path or f"{self.base_path.rstrip('/')}/events"
25
+ self._init_oauth_support()
26
+ self.runner = None
27
+ self.site = None
28
+ self.sessions: Dict[str, asyncio.Queue] = {}
29
+ self._external_setup = parent_app is not None
30
+ self.app.router.add_post(self.base_path, self._handle_http_request)
31
+ # Alias GET on base path to the SSE stream for clients that connect at /mcp
32
+ self.app.router.add_get(self.base_path, self._handle_sse, allow_head=True)
33
+ self.app.router.add_get(self.events_path, self._handle_sse, allow_head=True)
34
+ self.app.router.add_get("/", self._handle_info, allow_head=True)
35
+ if self.oauth_server:
36
+ self.oauth_server.register_routes(self.app)
37
+
38
+ async def start(self):
39
+ """Start the SSE MCP server."""
40
+ if self._external_setup:
41
+ self.logger.info("SSE MCP server using existing aiohttp application")
42
+ return
43
+
44
+ self.logger.info(
45
+ f"Starting SSE MCP server on {self.config.host}:{self.config.port}"
46
+ )
47
+
48
+ self.runner = web.AppRunner(self.app)
49
+ await self.runner.setup()
50
+
51
+ self.site = web.TCPSite(
52
+ self.runner,
53
+ self.config.host,
54
+ self.config.port
55
+ )
56
+ await self.site.start()
57
+
58
+ self.logger.info(f"SSE MCP server started at http://{self.config.host}:{self.config.port}")
59
+ self.logger.info(
60
+ "MCP endpoints: "
61
+ f"events at http://{self.config.host}:{self.config.port}{self.events_path}, "
62
+ f"requests at http://{self.config.host}:{self.config.port}{self.base_path}"
63
+ )
64
+
65
+ async def stop(self):
66
+ """Stop the SSE server."""
67
+ if not self._external_setup:
68
+ if self.site:
69
+ await self.site.stop()
70
+ if self.runner:
71
+ await self.runner.cleanup()
72
+
73
+ # Clear any pending sessions
74
+ for session_id, queue in list(self.sessions.items()):
75
+ with contextlib.suppress(Exception):
76
+ queue.put_nowait(None)
77
+ self.sessions.pop(session_id, None)
78
+
79
+ self.logger.info("SSE MCP server stopped")
80
+
81
+ async def _handle_info(self, request: web.Request) -> web.Response:
82
+ auth_response = self._authenticate_request(request)
83
+ if auth_response:
84
+ return auth_response
85
+
86
+ info = {
87
+ "name": self.config.name,
88
+ "version": self.config.version,
89
+ "description": self.config.description,
90
+ "transport": "sse",
91
+ "endpoint": self.events_path,
92
+ "tools": list(self.tools.keys()),
93
+ "tool_count": len(self.tools)
94
+ }
95
+ return web.json_response(info)
96
+
97
+ def _get_session_id(self, request: web.Request) -> str:
98
+ return request.headers.get("X-Session-Id") or request.query.get("session") or str(uuid.uuid4())
99
+
100
+ async def _handle_sse(self, request: web.Request) -> web.StreamResponse:
101
+ auth_response = self._authenticate_request(request)
102
+ if auth_response:
103
+ return auth_response
104
+
105
+ session_id = self._get_session_id(request)
106
+ queue: asyncio.Queue = asyncio.Queue()
107
+ self.sessions[session_id] = queue
108
+ self.logger.info(f"SSE client connected: {session_id}")
109
+
110
+ response = web.StreamResponse(
111
+ status=200,
112
+ headers={
113
+ "Content-Type": "text/event-stream",
114
+ "Cache-Control": "no-cache",
115
+ "Connection": "keep-alive",
116
+ "X-Session-Id": session_id,
117
+ },
118
+ )
119
+
120
+ await response.prepare(request)
121
+ await response.write(self._format_sse_event({"type": "connected", "session": session_id}, event="connection"))
122
+
123
+ try:
124
+ while True:
125
+ try:
126
+ message = await asyncio.wait_for(queue.get(), timeout=15)
127
+ if message is None:
128
+ break
129
+ await response.write(self._format_sse_event(message))
130
+ except asyncio.TimeoutError:
131
+ await response.write(b": keep-alive\n\n")
132
+ except asyncio.CancelledError:
133
+ self.logger.info(f"SSE client disconnected: {session_id}")
134
+ finally:
135
+ self.sessions.pop(session_id, None)
136
+ with contextlib.suppress(Exception):
137
+ await response.write_eof()
138
+
139
+ return response
140
+
141
+ def _format_sse_event(self, payload: Any, event: str = "message") -> bytes:
142
+ data = json.dumps(payload)
143
+ return f"event: {event}\ndata: {data}\n\n".encode("utf-8")
144
+
145
+ async def _push_to_session(self, session_id: Optional[str], message: Dict[str, Any]):
146
+ if not session_id:
147
+ return
148
+ if queue := self.sessions.get(session_id):
149
+ try:
150
+ queue.put_nowait(message)
151
+ except Exception as e:
152
+ self.logger.warning(f"Failed to enqueue SSE message for {session_id}: {e}")
153
+
154
+ async def _handle_http_request(self, request: web.Request) -> web.Response:
155
+ try:
156
+ auth_response = self._authenticate_request(request)
157
+ if auth_response:
158
+ return auth_response
159
+
160
+ data = await request.json()
161
+ method = data.get("method")
162
+ params = data.get("params", {})
163
+ request_id = data.get("id")
164
+ session_id = request.headers.get("X-Session-Id") or request.query.get("session")
165
+
166
+ self.logger.info(f"SSE HTTP request: {method} (session {session_id or 'none'})")
167
+
168
+ try:
169
+ if method == "initialize":
170
+ result = await self.handle_initialize(params)
171
+ elif method == "tools/list":
172
+ result = await self.handle_tools_list(params)
173
+ elif method == "notifications/initialized":
174
+ # This is a notification, no response needed
175
+ self.logger.info("Client initialization complete")
176
+ return web.Response(status=204) # No Content
177
+ elif method == "tools/call":
178
+ result = await self.handle_tools_call(params)
179
+ else:
180
+ raise RuntimeError(
181
+ f"Unknown method: {method}"
182
+ )
183
+
184
+ response = {
185
+ "jsonrpc": "2.0",
186
+ "id": request_id,
187
+ "result": result
188
+ }
189
+
190
+ except Exception as e:
191
+ self.logger.error(f"Error handling {method}: {e}")
192
+ response = {
193
+ "jsonrpc": "2.0",
194
+ "id": request_id,
195
+ "error": {
196
+ "code": -32603,
197
+ "message": str(e)
198
+ }
199
+ }
200
+
201
+ await self._push_to_session(session_id, response)
202
+ return web.json_response(response)
203
+
204
+ except Exception as e:
205
+ self.logger.error(f"SSE HTTP request error: {e}")
206
+ return web.json_response(
207
+ {
208
+ "jsonrpc": "2.0",
209
+ "id": None,
210
+ "error": {
211
+ "code": -32700,
212
+ "message": "Parse error",
213
+ }
214
+ },
215
+ )
216
+
217
+
218
+ class SseMCPSession(HttpMCPSession):
219
+ """MCP session using SSE (Server-Sent Events) for transport."""
220
+
221
+ def __init__(self, config: MCPClientConfig, logger):
222
+ super().__init__(config, logger)
223
+ self._sse_task = None
224
+ self._session_id = str(uuid.uuid4())
225
+ self._sse_ready = asyncio.Event()
226
+
227
+ async def connect(self):
228
+ """Connect to MCP server via SSE + HTTP."""
229
+ try:
230
+ # 1. Setup headers (auth etc) - reusing logic from HttpMCPSession
231
+ # We need to manually trigger auth setup if we want headers for SSE connection
232
+ if self.config.auth_type:
233
+ from parrot.mcp.client import MCPAuthHandler
234
+ self._auth_handler = MCPAuthHandler(
235
+ self.config.auth_type,
236
+ self.config.auth_config
237
+ )
238
+ auth_headers = await self._auth_handler.get_auth_headers()
239
+ self._base_headers.update(auth_headers)
240
+
241
+ self._base_headers.update(self.config.headers)
242
+
243
+ # Add Session ID to headers for correlation
244
+ self._base_headers["X-Session-Id"] = self._session_id
245
+
246
+ # 2. Start SSE Listener
247
+ self._sse_task = asyncio.create_task(self._listen_sse())
248
+
249
+ # Wait for connection to be ready
250
+ try:
251
+ await asyncio.wait_for(self._sse_ready.wait(), timeout=self.config.timeout)
252
+ except asyncio.TimeoutError:
253
+ self.logger.warning("Timeout waiting for SSE connection ready, proceeding anyway")
254
+
255
+ # 3. Create HTTP session for POST requests
256
+ timeout = aiohttp.ClientTimeout(total=self.config.timeout)
257
+ self._session = aiohttp.ClientSession(
258
+ timeout=timeout,
259
+ headers=self._base_headers
260
+ )
261
+
262
+ # 4. Initialize MCP
263
+ await self._initialize_session()
264
+ self._initialized = True
265
+ self.logger.info(f"SSE connection established to {self.config.name} (session {self._session_id})")
266
+
267
+ except Exception as e:
268
+ await self.disconnect()
269
+ raise MCPConnectionError(f"SSE connection failed: {e}") from e
270
+
271
+ async def _listen_sse(self):
272
+ """Listen for SSE events."""
273
+ events_url = self.config.events_path
274
+ if not events_url:
275
+ events_url = self.config.url
276
+
277
+ self.logger.debug(f"Connecting to SSE endpoint: {events_url}")
278
+
279
+ try:
280
+ async with sse_client.EventSource(
281
+ events_url,
282
+ headers=self._base_headers
283
+ ) as event_source:
284
+ self._sse_ready.set()
285
+
286
+ async for event in event_source:
287
+ try:
288
+ if event.type == 'error':
289
+ self.logger.error(f"SSE Error: {event.data}")
290
+ continue
291
+
292
+ if event.type == 'connection':
293
+ self.logger.debug(f"SSE Connection event: {event.data}")
294
+ continue
295
+
296
+ if event.type == 'message':
297
+ await self._handle_sse_message(event.data)
298
+
299
+ except Exception as e:
300
+ self.logger.error(f"Error processing SSE event: {e}")
301
+
302
+ except asyncio.CancelledError:
303
+ self.logger.debug("SSE listener cancelled")
304
+ except Exception as e:
305
+ self.logger.error(f"SSE connection error: {e}")
306
+
307
+ async def _handle_sse_message(self, data: str):
308
+ """Handle incoming JSON-RPC message from SSE."""
309
+ try:
310
+ message = json.loads(data)
311
+
312
+ if "method" in message and "id" not in message:
313
+ self.logger.info(f"Received notification: {message['method']}")
314
+ elif "method" in message and "id" in message:
315
+ self.logger.info(f"Received server request: {message['method']}")
316
+
317
+ except json.JSONDecodeError:
318
+ self.logger.error("Failed to decode SSE message")
319
+
320
+ async def disconnect(self):
321
+ """Disconnect SSE session."""
322
+ if self._sse_task:
323
+ self._sse_task.cancel()
324
+ try:
325
+ await self._sse_task
326
+ except asyncio.CancelledError:
327
+ pass
328
+ self._sse_task = None
329
+
330
+ await super().disconnect()
@@ -0,0 +1,309 @@
1
+ import sys
2
+ import os
3
+ import asyncio
4
+ import json
5
+ import logging
6
+ from typing import Dict, Any, Optional
7
+
8
+ from parrot.mcp.config import MCPServerConfig
9
+ from parrot.mcp.transports.base import MCPServerBase
10
+ from parrot.mcp.client import MCPClientConfig, MCPConnectionError
11
+
12
+ class StdioMCPServer(MCPServerBase):
13
+ """MCP server using stdio transport."""
14
+
15
+ def __init__(self, config: MCPServerConfig):
16
+ super().__init__(config)
17
+ self._request_id = 0
18
+ self._running = False
19
+
20
+ async def start(self):
21
+ """Start the stdio MCP server."""
22
+ self.logger.info(f"Starting stdio MCP server with {len(self.tools)} tools...")
23
+ self._running = True
24
+
25
+ while self._running:
26
+ try:
27
+ # Read line from stdin
28
+ line = sys.stdin.readline()
29
+ if not line:
30
+ break
31
+
32
+ line = line.strip()
33
+ if not line:
34
+ continue
35
+
36
+ # Parse JSON-RPC request
37
+ try:
38
+ request = json.loads(line)
39
+ response = await self._handle_request(request)
40
+
41
+ if response:
42
+ print(json.dumps(response), flush=True)
43
+
44
+ except json.JSONDecodeError as e:
45
+ self.logger.warning(f"Invalid JSON received: {e}")
46
+ continue
47
+
48
+ except KeyboardInterrupt:
49
+ break
50
+ except Exception as e:
51
+ self.logger.error(f"Error in main loop: {e}")
52
+ continue
53
+
54
+ self.logger.info("Stdio MCP server stopped")
55
+
56
+ async def stop(self):
57
+ """Stop the stdio server."""
58
+ self._running = False
59
+
60
+ async def _handle_request(self, request: Dict[str, Any]) -> Optional[Dict[str, Any]]:
61
+ """Handle a JSON-RPC request."""
62
+ method = request.get("method")
63
+ params = request.get("params", {})
64
+ request_id = request.get("id")
65
+
66
+ try:
67
+ if method == "initialize":
68
+ result = await self.handle_initialize(params)
69
+ elif method == "tools/list":
70
+ result = await self.handle_tools_list(params)
71
+ elif method == "tools/call":
72
+ result = await self.handle_tools_call(params)
73
+ elif method == "notifications/initialized":
74
+ # This is a notification, no response needed
75
+ self.logger.info("Client initialization complete")
76
+ return None
77
+ else:
78
+ raise RuntimeError(f"Unknown method: {method}")
79
+
80
+ # Return success response
81
+ return {
82
+ "jsonrpc": "2.0",
83
+ "id": request_id,
84
+ "result": result
85
+ }
86
+
87
+ except Exception as e:
88
+ self.logger.error(f"Error handling {method}: {e}")
89
+ return {
90
+ "jsonrpc": "2.0",
91
+ "id": request_id,
92
+ "error": {
93
+ "code": -32603,
94
+ "message": str(e)
95
+ }
96
+ }
97
+
98
+
99
+ class StdioMCPSession:
100
+ """MCP session for stdio transport."""
101
+
102
+ def __init__(self, config: MCPClientConfig, logger):
103
+ self.config = config
104
+ self.logger = logger
105
+ self._request_id = 0
106
+ self._process = None
107
+ self._stdin = None
108
+ self._stdout = None
109
+ self._stderr = None
110
+ self._initialized = False
111
+
112
+ async def connect(self):
113
+ """Connect to MCP server via stdio."""
114
+ if self._process:
115
+ await self.disconnect()
116
+
117
+ try:
118
+ await self._start_process()
119
+ await self._initialize_session()
120
+ self._initialized = True
121
+ self.logger.info(f"Stdio connection established to {self.config.name}")
122
+
123
+ except Exception as e:
124
+ await self.disconnect()
125
+ raise MCPConnectionError(f"Stdio connection failed: {e}") from e
126
+
127
+ async def _start_process(self):
128
+ """Start the MCP server process."""
129
+ if not self.config.command:
130
+ raise ValueError("Command required for stdio transport")
131
+
132
+ args = self.config.args or []
133
+ env = dict(os.environ)
134
+ if self.config.env:
135
+ env.update(self.config.env)
136
+
137
+ self._process = await asyncio.create_subprocess_exec(
138
+ self.config.command,
139
+ *args,
140
+ stdin=asyncio.subprocess.PIPE,
141
+ stdout=asyncio.subprocess.PIPE,
142
+ stderr=asyncio.subprocess.PIPE,
143
+ env=env
144
+ )
145
+
146
+ self._stdin = self._process.stdin
147
+ self._stdout = self._process.stdout
148
+ self._stderr = self._process.stderr
149
+
150
+ await asyncio.sleep(self.config.startup_delay)
151
+
152
+ if self._process.returncode is not None:
153
+ stderr_output = ""
154
+ if self._stderr:
155
+ try:
156
+ stderr_data = await asyncio.wait_for(self._stderr.read(1024), timeout=2.0)
157
+ stderr_output = stderr_data.decode('utf-8', errors='replace')
158
+ except asyncio.TimeoutError:
159
+ stderr_output = "No error output available"
160
+
161
+ raise RuntimeError(f"Process failed to start: {stderr_output}")
162
+
163
+ async def _initialize_session(self):
164
+ """Initialize the MCP session."""
165
+ try:
166
+ await self._send_request("initialize", {
167
+ "protocolVersion": "2024-11-05",
168
+ "capabilities": {"tools": {}},
169
+ "clientInfo": {"name": "ai-parrot-mcp-client", "version": "1.0.0"}
170
+ })
171
+ await self._send_notification("notifications/initialized")
172
+ except Exception as e:
173
+ raise MCPConnectionError(f"Session initialization failed: {e}") from e
174
+
175
+ async def _send_request(self, method: str, params: dict = None) -> dict:
176
+ if not self._process or self._process.returncode is not None:
177
+ raise MCPConnectionError("Process is not running")
178
+
179
+ request_id = self._get_next_id()
180
+ request = {"jsonrpc": "2.0", "id": request_id, "method": method}
181
+ if params:
182
+ request["params"] = params
183
+
184
+ loop = asyncio.get_running_loop()
185
+ deadline = loop.time() + self.config.timeout
186
+
187
+ try:
188
+ line = (json.dumps(request) + "\n").encode("utf-8")
189
+ self.logger.debug(f"Stdio sending: {line.decode().strip()}")
190
+ self._stdin.write(line)
191
+ await self._stdin.drain()
192
+
193
+ response = None
194
+ while True:
195
+ timeout = max(0.1, deadline - loop.time())
196
+ response_line = await asyncio.wait_for(self._stdout.readline(), timeout=timeout)
197
+ if not response_line:
198
+ raise MCPConnectionError("Empty response - connection closed")
199
+
200
+ response_str = response_line.decode("utf-8", errors="replace").strip()
201
+ if not response_str:
202
+ continue
203
+ self.logger.debug(f"Stdio received: {response_str}")
204
+
205
+ # Skip non-JSON garbage
206
+ try:
207
+ candidate = json.loads(response_str)
208
+ except json.JSONDecodeError:
209
+ self.logger.debug(f"Ignoring non-JSON stdout: {response_str!r}")
210
+ continue
211
+
212
+ # Only accept responses with our request id; ignore notifications/others
213
+ if candidate.get("id") != request_id:
214
+ # could be a notification or another message; ignore
215
+ continue
216
+
217
+ response = candidate
218
+ break
219
+
220
+ if "error" in response:
221
+ raise MCPConnectionError(f"Server error: {response['error']}")
222
+
223
+ return response.get("result", {})
224
+
225
+ except asyncio.TimeoutError:
226
+ raise MCPConnectionError(f"Request timeout after {self.config.timeout} seconds")
227
+ except Exception as e:
228
+ if isinstance(e, MCPConnectionError):
229
+ raise
230
+ raise MCPConnectionError(f"Request failed: {e}") from e
231
+
232
+ async def _send_notification(self, method: str, params: dict = None):
233
+ """Send JSON-RPC notification."""
234
+ notification = {"jsonrpc": "2.0", "method": method}
235
+ if params:
236
+ notification["params"] = params
237
+
238
+ notification_line = json.dumps(notification) + "\n"
239
+ self.logger.debug(f"Stdio notification: {notification_line.strip()}")
240
+
241
+ self._stdin.write(notification_line.encode('utf-8'))
242
+ await self._stdin.drain()
243
+
244
+ def _get_next_id(self):
245
+ self._request_id += 1
246
+ return self._request_id
247
+
248
+ async def list_tools(self):
249
+ """List available tools."""
250
+ if not self._initialized:
251
+ raise MCPConnectionError("Session not initialized")
252
+
253
+ result = await self._send_request("tools/list")
254
+ tools = result.get("tools", [])
255
+
256
+ tool_objects = []
257
+ for tool_dict in tools:
258
+ tool_obj = type('MCPTool', (), tool_dict)()
259
+ tool_objects.append(tool_obj)
260
+
261
+ return tool_objects
262
+
263
+ async def call_tool(self, tool_name: str, arguments: dict):
264
+ """Call a tool."""
265
+ if not self._initialized:
266
+ raise MCPConnectionError("Session not initialized")
267
+
268
+ result = await self._send_request("tools/call", {
269
+ "name": tool_name,
270
+ "arguments": arguments
271
+ })
272
+
273
+ content_items = []
274
+ if "content" in result:
275
+ for item in result["content"]:
276
+ content_obj = type('ContentItem', (), item)()
277
+ content_items.append(content_obj)
278
+
279
+ result_obj = type('ToolCallResult', (), {"content": content_items})()
280
+ return result_obj
281
+
282
+ async def disconnect(self):
283
+ """Disconnect stdio session."""
284
+ self._initialized = False
285
+
286
+ if self._process:
287
+ try:
288
+ if self._stdin and not self._stdin.is_closing():
289
+ self._stdin.close()
290
+ await self._stdin.wait_closed()
291
+
292
+ if self._process.returncode is None:
293
+ try:
294
+ await asyncio.wait_for(
295
+ self._process.wait(),
296
+ timeout=self.config.kill_timeout
297
+ )
298
+ except asyncio.TimeoutError:
299
+ self.logger.warning("Process didn't terminate, force killing")
300
+ self._process.kill()
301
+ await self._process.wait()
302
+
303
+ except Exception as e:
304
+ self.logger.debug(f"Error during disconnect: {e}")
305
+ finally:
306
+ self._process = None
307
+ self._stdin = None
308
+ self._stdout = None
309
+ self._stderr = None