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
parrot/mcp/server.py ADDED
@@ -0,0 +1,225 @@
1
+ """
2
+ MCP Server Implementation - Expose AI-Parrot Tools via MCP Protocol
3
+ =================================================================
4
+ This creates an MCP server that exposes your existing AbstractTool instances
5
+ as MCP tools that can be consumed by any MCP client.
6
+ """
7
+ import logging
8
+ import asyncio
9
+ import argparse
10
+ from typing import List, Optional
11
+ from aiohttp import web
12
+
13
+ # AI-Parrot imports
14
+ from parrot.tools.abstract import AbstractTool
15
+
16
+ # Config
17
+ from parrot.mcp.config import MCPServerConfig
18
+
19
+ # Transports
20
+ from parrot.mcp.transports.stdio import StdioMCPServer
21
+ from parrot.mcp.transports.http import HttpMCPServer
22
+ from parrot.mcp.transports.sse import SseMCPServer
23
+ from parrot.mcp.transports.unix import UnixMCPServer
24
+ from parrot.mcp.transports.quic import QuicMCPServer
25
+
26
+ # Suppress noisy loggers
27
+ logging.getLogger('matplotlib').setLevel(logging.ERROR)
28
+ logging.getLogger('PIL').setLevel(logging.ERROR)
29
+ logging.getLogger('urllib3').setLevel(logging.ERROR)
30
+ logging.getLogger('requests').setLevel(logging.ERROR)
31
+
32
+
33
+ class MCPServer:
34
+ """Main MCP server class that chooses transport."""
35
+
36
+ def __init__(self, config: MCPServerConfig, parent_app: Optional[web.Application] = None):
37
+ self.config = config
38
+
39
+ if config.transport == "stdio":
40
+ self.server = StdioMCPServer(config)
41
+ elif config.transport == "http":
42
+ self.server = HttpMCPServer(config, parent_app=parent_app)
43
+ elif config.transport == "sse":
44
+ self.server = SseMCPServer(config, parent_app=parent_app)
45
+ elif config.transport == "unix":
46
+ self.server = UnixMCPServer(config)
47
+ elif config.transport == "quic":
48
+ self.server = QuicMCPServer(config)
49
+ else:
50
+ raise ValueError(
51
+ f"Unsupported transport: {config.transport}"
52
+ )
53
+
54
+ def register_tool(self, tool: AbstractTool):
55
+ """Register a tool."""
56
+ self.server.register_tool(tool)
57
+
58
+ def register_tools(self, tools: List[AbstractTool]):
59
+ """Register multiple tools."""
60
+ self.server.register_tools(tools)
61
+
62
+ async def start(self):
63
+ """Start the server."""
64
+ await self.server.start()
65
+
66
+ async def stop(self):
67
+ """Start the server."""
68
+ await self.server.stop()
69
+
70
+ async def __aenter__(self):
71
+ await self.start()
72
+ return self
73
+
74
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
75
+ await self.stop()
76
+
77
+
78
+ # Convenience functions
79
+
80
+ def create_stdio_mcp_server(
81
+ name: str = "ai-parrot-tools",
82
+ tools: Optional[List[AbstractTool]] = None,
83
+ **kwargs
84
+ ) -> MCPServer:
85
+ """Create a stdio MCP server."""
86
+ config = MCPServerConfig(
87
+ name=name,
88
+ transport="stdio",
89
+ **kwargs
90
+ )
91
+
92
+ server = MCPServer(config)
93
+
94
+ if tools:
95
+ server.register_tools(tools)
96
+
97
+ return server
98
+
99
+
100
+ def create_http_mcp_server(
101
+ name: str = "ai-parrot-tools",
102
+ host: str = "localhost",
103
+ port: int = 8080,
104
+ tools: Optional[List[AbstractTool]] = None,
105
+ parent_app: Optional[web.Application] = None,
106
+ **kwargs
107
+ ) -> MCPServer:
108
+ """Create an HTTP MCP server."""
109
+ config = MCPServerConfig(
110
+ name=name,
111
+ transport="http",
112
+ host=host,
113
+ port=port,
114
+ **kwargs
115
+ )
116
+
117
+ server = HttpMCPServer(
118
+ config,
119
+ parent_app=parent_app
120
+ )
121
+
122
+ if tools:
123
+ server.register_tools(tools)
124
+
125
+ return server
126
+
127
+ def create_sse_mcp_server(
128
+ name: str = "ai-parrot-tools",
129
+ host: str = "localhost",
130
+ port: int = 8080,
131
+ tools: Optional[List[AbstractTool]] = None,
132
+ parent_app: Optional[web.Application] = None,
133
+ **kwargs,
134
+ ) -> MCPServer:
135
+ """Create an SSE MCP server."""
136
+ config = MCPServerConfig(
137
+ name=name,
138
+ transport="sse",
139
+ host=host,
140
+ port=port,
141
+ **kwargs,
142
+ )
143
+
144
+ server = MCPServer(config, parent_app=parent_app)
145
+ if tools:
146
+ server.register_tools(tools)
147
+
148
+ return server
149
+
150
+ def create_unix_mcp_server(
151
+ name: str = "ai-parrot-tools",
152
+ socket_path: Optional[str] = None,
153
+ tools: Optional[List[AbstractTool]] = None,
154
+ **kwargs,
155
+ ) -> MCPServer:
156
+ """Create an Unix MCP server."""
157
+ config = MCPServerConfig(
158
+ name=name,
159
+ transport="unix",
160
+ socket_path=socket_path,
161
+ **kwargs,
162
+ )
163
+
164
+ server = MCPServer(config)
165
+ if tools:
166
+ server.register_tools(tools)
167
+
168
+ return server
169
+
170
+ # CLI support
171
+ async def main():
172
+ """Main CLI entry point."""
173
+ parser = argparse.ArgumentParser(
174
+ description="AI-Parrot MCP Server"
175
+ )
176
+ parser.add_argument("--transport", choices=["stdio", "http", "sse"], default="stdio",
177
+ help="Transport type")
178
+ parser.add_argument("--host", default="localhost",
179
+ help="Host for HTTP server")
180
+ parser.add_argument("--port", type=int, default=8080,
181
+ help="Port for HTTP server")
182
+ parser.add_argument("--name", default="ai-parrot-tools",
183
+ help="Server name")
184
+ parser.add_argument("--log-level", default="INFO",
185
+ help="Log level")
186
+
187
+ args = parser.parse_args()
188
+
189
+ # Create server config
190
+ config = MCPServerConfig(
191
+ name=args.name,
192
+ transport=args.transport,
193
+ host=args.host,
194
+ port=args.port,
195
+ log_level=args.log_level
196
+ )
197
+
198
+ # Create server
199
+ server = MCPServer(config)
200
+
201
+ # Register example tools:
202
+ # server.register_tool(YourOpenWeatherTool())
203
+ # server.register_tool(YourDatabaseQueryTool())
204
+
205
+ try:
206
+ if args.transport in {"http", "sse"}:
207
+ await server.start()
208
+ print(f"Server running at http://{args.host}:{args.port}")
209
+ print("Press Ctrl+C to stop")
210
+
211
+ # Keep running
212
+ while True:
213
+ await asyncio.sleep(1)
214
+ else:
215
+ # For stdio, just start and let it handle stdin
216
+ await server.start()
217
+
218
+ except KeyboardInterrupt:
219
+ print("\nShutting down...")
220
+ finally:
221
+ await server.stop()
222
+
223
+
224
+ if __name__ == "__main__":
225
+ asyncio.run(main())
@@ -0,0 +1,3 @@
1
+ from .websocket import WebSocketMCPServer, WebSocketMCPSession
2
+
3
+ __all__ = ['WebSocketMCPServer', 'WebSocketMCPSession']
@@ -0,0 +1,279 @@
1
+ from abc import ABC, abstractmethod
2
+ import logging
3
+ import time
4
+ from typing import Dict, List, Optional, Any
5
+ from aiohttp import web
6
+
7
+ from parrot.tools.abstract import AbstractTool
8
+ from parrot.mcp.config import MCPServerConfig, AuthMethod
9
+ from parrot.mcp.adapter import MCPToolAdapter
10
+ from parrot.mcp.oauth import OAuthAuthorizationServer, APIKeyStore, ExternalOAuthValidator
11
+
12
+
13
+ class MCPServerBase(ABC):
14
+ """Base class for MCP servers."""
15
+
16
+ def __init__(self, config: MCPServerConfig):
17
+ self.config = config
18
+ self.tools: Dict[str, MCPToolAdapter] = {}
19
+ self.logger = logging.getLogger(f"MCPServer.{config.name}")
20
+ log_level = getattr(logging, config.log_level.upper(), logging.WARNING)
21
+ self.logger.setLevel(log_level)
22
+
23
+ # Authentication components
24
+ self.oauth_server: Optional[OAuthAuthorizationServer] = None
25
+ self.api_key_store: Optional[APIKeyStore] = None
26
+ self.external_oauth: Optional[ExternalOAuthValidator] = None
27
+
28
+ # Initialize authentication based on method
29
+ self._init_authentication()
30
+
31
+ def _init_authentication(self) -> None:
32
+ """Initialize authentication based on config.auth_method."""
33
+ auth_method = self.config.auth_method
34
+
35
+ # Backward compatibility: enable_oauth maps to OAUTH2_INTERNAL
36
+ if self.config.enable_oauth and auth_method == AuthMethod.NONE:
37
+ auth_method = AuthMethod.OAUTH2_INTERNAL
38
+ self.config.auth_method = auth_method
39
+
40
+ if auth_method == AuthMethod.API_KEY:
41
+ self.api_key_store = self.config.api_key_store or APIKeyStore()
42
+ self.logger.info("Authentication: API Key enabled")
43
+
44
+ elif auth_method == AuthMethod.OAUTH2_INTERNAL:
45
+ self.oauth_server = OAuthAuthorizationServer(
46
+ default_scopes=self.config.oauth_scopes,
47
+ allow_dynamic_registration=self.config.oauth_allow_dynamic_registration,
48
+ token_ttl=self.config.oauth_token_ttl,
49
+ code_ttl=self.config.oauth_code_ttl,
50
+ )
51
+ self.logger.info("Authentication: OAuth2 (internal) enabled")
52
+
53
+ elif auth_method == AuthMethod.OAUTH2_EXTERNAL:
54
+ if not self.config.oauth2_introspection_endpoint:
55
+ raise ValueError(
56
+ "oauth2_introspection_endpoint required for OAUTH2_EXTERNAL"
57
+ )
58
+ self.external_oauth = ExternalOAuthValidator(
59
+ introspection_endpoint=self.config.oauth2_introspection_endpoint,
60
+ client_id=self.config.oauth2_client_id or "",
61
+ client_secret=self.config.oauth2_client_secret or "",
62
+ resource_server_url=self.config.oauth2_resource_server_url,
63
+ )
64
+ self.logger.info(
65
+ f"Authentication: OAuth2 (external) enabled - {self.config.oauth2_issuer_url}"
66
+ )
67
+
68
+ elif auth_method == AuthMethod.BEARER:
69
+ self.logger.info("Authentication: Bearer (navigator-auth) enabled")
70
+
71
+ else:
72
+ self.logger.debug("Authentication: None (open access)")
73
+
74
+ def register_tool(self, tool: AbstractTool):
75
+ """Register an AI-Parrot tool with the MCP server."""
76
+ tool_name = tool.name
77
+
78
+ # Apply filtering
79
+ if self.config.allowed_tools and tool_name not in self.config.allowed_tools:
80
+ self.logger.info(f"Skipping tool {tool_name} (not in allowed_tools)")
81
+ return
82
+
83
+ if self.config.blocked_tools and tool_name in self.config.blocked_tools:
84
+ self.logger.info(f"Skipping tool {tool_name} (in blocked_tools)")
85
+ return
86
+
87
+ adapter = MCPToolAdapter(tool)
88
+ self.tools[tool_name] = adapter
89
+ self.logger.info(f"Registered tool: {tool_name}")
90
+
91
+ def register_tools(self, tools: List[AbstractTool]):
92
+ """Register multiple tools."""
93
+ for tool in tools:
94
+ self.register_tool(tool)
95
+
96
+
97
+ async def _authenticate_request(self, request: web.Request) -> Optional[web.Response]:
98
+ """
99
+ Authenticate request based on configured auth method.
100
+
101
+ Returns None if authenticated, or a web.Response with error if not.
102
+ """
103
+ auth_method = self.config.auth_method
104
+
105
+ if auth_method == AuthMethod.NONE:
106
+ return None
107
+
108
+ elif auth_method == AuthMethod.API_KEY:
109
+ return await self._authenticate_api_key(request)
110
+
111
+ elif auth_method == AuthMethod.OAUTH2_INTERNAL:
112
+ return self._authenticate_oauth_internal(request)
113
+
114
+ elif auth_method == AuthMethod.OAUTH2_EXTERNAL:
115
+ return await self._authenticate_oauth_external(request)
116
+
117
+ elif auth_method == AuthMethod.BEARER:
118
+ return await self._authenticate_bearer(request)
119
+
120
+ return None
121
+
122
+ async def _authenticate_api_key(self, request: web.Request) -> Optional[web.Response]:
123
+ """Validate API key from header."""
124
+ api_key = request.headers.get(self.config.api_key_header)
125
+ if not api_key:
126
+ return self._unauthorized_response(
127
+ "API key required",
128
+ f'X-API-Key realm="mcp"'
129
+ )
130
+
131
+ record = self.api_key_store.validate_key(api_key)
132
+ if not record:
133
+ return self._unauthorized_response("Invalid or expired API key")
134
+
135
+ # Log session start
136
+ self.api_key_store.log_session_start(api_key, record.user_id, time.time())
137
+ self.logger.debug(f"API key authenticated for user: {record.user_id}")
138
+
139
+ # Store user info in request for downstream use
140
+ request["mcp_user"] = {"user_id": record.user_id, "scopes": record.scopes}
141
+ return None
142
+
143
+ def _authenticate_oauth_internal(self, request: web.Request) -> Optional[web.Response]:
144
+ """Validate OAuth access token from internal OAuth server."""
145
+ if not self.oauth_server:
146
+ return None
147
+
148
+ token = self.oauth_server.bearer_token_from_header(
149
+ request.headers.get("Authorization")
150
+ )
151
+ if not self.oauth_server.is_token_valid(token):
152
+ return self._unauthorized_response("Valid Bearer token is required")
153
+
154
+ return None
155
+
156
+ async def _authenticate_oauth_external(self, request: web.Request) -> Optional[web.Response]:
157
+ """Validate OAuth access token via external introspection."""
158
+ if not self.external_oauth:
159
+ return None
160
+
161
+ token = self._extract_bearer_token(request.headers.get("Authorization"))
162
+ if not token:
163
+ return self._unauthorized_response("Bearer token required")
164
+
165
+ token_info = await self.external_oauth.validate_token(token)
166
+ if not token_info:
167
+ return self._unauthorized_response("Invalid or expired token")
168
+
169
+ # Store token info in request
170
+ request["mcp_user"] = {
171
+ "user_id": token_info.get("sub") or token_info.get("client_id"),
172
+ "scopes": token_info.get("scope", "").split() if token_info.get("scope") else [],
173
+ "token_info": token_info,
174
+ }
175
+ return None
176
+
177
+ async def _authenticate_bearer(self, request: web.Request) -> Optional[web.Response]:
178
+ """Validate bearer token via navigator-auth."""
179
+ auth = request.app.get('auth')
180
+ if not auth:
181
+ self.logger.warning("navigator-auth not configured in app['auth']")
182
+ # Fall through if auth not configured (development mode)
183
+ return None
184
+
185
+ try:
186
+ userdata = await auth.get_session(request)
187
+ if not userdata:
188
+ return web.json_response(
189
+ {"error": "unauthorized", "error_description": "Session required"},
190
+ status=401,
191
+ headers={"WWW-Authenticate": 'Bearer realm="mcp"'}
192
+ )
193
+
194
+ # Store user info in request
195
+ request["mcp_user"] = userdata
196
+ return None
197
+
198
+ except Exception as e:
199
+ self.logger.error(f"navigator-auth error: {e}")
200
+ return web.json_response(
201
+ {"error": "unauthorized", "error_description": "Authentication failed"},
202
+ status=401,
203
+ headers={"WWW-Authenticate": 'Bearer realm="mcp"'}
204
+ )
205
+
206
+ def _extract_bearer_token(self, auth_header: Optional[str]) -> Optional[str]:
207
+ """Extract bearer token from Authorization header."""
208
+ if not auth_header:
209
+ return None
210
+ if not auth_header.lower().startswith("bearer "):
211
+ return None
212
+ return auth_header.split(" ", 1)[1].strip()
213
+
214
+ def _unauthorized_response(
215
+ self,
216
+ message: str,
217
+ www_authenticate: str = 'Bearer realm="mcp"'
218
+ ) -> web.Response:
219
+ """Create a 401 unauthorized response."""
220
+ return web.json_response(
221
+ {"error": "unauthorized", "error_description": message},
222
+ status=401,
223
+ headers={"WWW-Authenticate": www_authenticate}
224
+ )
225
+
226
+
227
+ async def handle_initialize(self, params: Dict[str, Any]) -> Dict[str, Any]:
228
+ """Handle MCP initialize request."""
229
+ self.logger.info("Initializing MCP server...")
230
+
231
+ return {
232
+ "protocolVersion": "2024-11-05",
233
+ "capabilities": {
234
+ "tools": {
235
+ "listChanged": False
236
+ }
237
+ },
238
+ "serverInfo": {
239
+ "name": self.config.name,
240
+ "version": self.config.version,
241
+ "description": self.config.description
242
+ }
243
+ }
244
+
245
+ async def handle_tools_list(self, params: Dict[str, Any]) -> Dict[str, Any]:
246
+ """Handle tools/list request."""
247
+ self.logger.info(f"Listing {len(self.tools)} available tools")
248
+
249
+ tools = []
250
+ tools.extend(
251
+ adapter.to_mcp_tool_definition() for adapter in self.tools.values()
252
+ )
253
+
254
+ return {"tools": tools}
255
+
256
+ async def handle_tools_call(self, params: Dict[str, Any]) -> Dict[str, Any]:
257
+ """Handle tools/call request."""
258
+ tool_name = params.get("name")
259
+ arguments = params.get("arguments", {})
260
+
261
+ self.logger.info(f"Calling tool: {tool_name} with args: {arguments}")
262
+
263
+ if tool_name not in self.tools:
264
+ raise RuntimeError(
265
+ f"Tool not found: {tool_name}"
266
+ )
267
+
268
+ adapter = self.tools[tool_name]
269
+ return await adapter.execute(arguments)
270
+
271
+ @abstractmethod
272
+ async def start(self):
273
+ """Start the MCP server."""
274
+ pass
275
+
276
+ @abstractmethod
277
+ async def stop(self):
278
+ """Stop the MCP server."""
279
+ pass
@@ -0,0 +1,163 @@
1
+ import grpc
2
+ import asyncio
3
+ from typing import Dict, Any, Optional
4
+ from dataclasses import dataclass
5
+ # from .proto import mcp_pb2, mcp_pb2_grpc
6
+
7
+
8
+ @dataclass
9
+ class GrpcMCPConfig:
10
+ """Configuration for gRPC MCP transport."""
11
+ host: str = "localhost"
12
+ port: int = 50051
13
+ use_tls: bool = True
14
+ cert_path: Optional[str] = None
15
+ # For protobuf message format instead of JSON-RPC
16
+ use_protobuf_messages: bool = False
17
+
18
+
19
+ class GrpcMCPSession:
20
+ """MCP session for gRPC transport with optional protobuf messages."""
21
+
22
+ def __init__(self, config: 'MCPServerConfig', logger):
23
+ self.config = config
24
+ self.logger = logger
25
+ self._request_id = 0
26
+ self._channel: Optional[grpc.aio.Channel] = None
27
+ self._stub = None
28
+ self._initialized = False
29
+ self._response_queue: asyncio.Queue = asyncio.Queue()
30
+ self._stream_task: Optional[asyncio.Task] = None
31
+
32
+ async def connect(self):
33
+ """Connect to MCP server via gRPC."""
34
+ try:
35
+ grpc_config = self.config.grpc_config or GrpcMCPConfig()
36
+
37
+ # Create channel
38
+ target = f"{grpc_config.host}:{grpc_config.port}"
39
+
40
+ if grpc_config.use_tls:
41
+ if grpc_config.cert_path:
42
+ with open(grpc_config.cert_path, 'rb') as f:
43
+ creds = grpc.ssl_channel_credentials(f.read())
44
+ else:
45
+ creds = grpc.ssl_channel_credentials()
46
+ self._channel = grpc.aio.secure_channel(target, creds)
47
+ else:
48
+ self._channel = grpc.aio.insecure_channel(target)
49
+
50
+ # Create stub
51
+ self._stub = mcp_pb2_grpc.MCPServiceStub(self._channel)
52
+
53
+ # Start bidirectional stream
54
+ self._stream = self._stub.BiDirectionalStream()
55
+ self._stream_task = asyncio.create_task(self._read_responses())
56
+
57
+ # Initialize MCP session
58
+ await self._initialize_session()
59
+ self._initialized = True
60
+
61
+ self.logger.info(f"gRPC connection established to {self.config.name}")
62
+
63
+ except Exception as e:
64
+ await self.disconnect()
65
+ raise MCPConnectionError(f"gRPC connection failed: {e}") from e
66
+
67
+ async def _read_responses(self):
68
+ """Background task to read responses from gRPC stream."""
69
+ try:
70
+ async for response in self._stream:
71
+ # Convert protobuf to dict if needed
72
+ if hasattr(response, 'json_rpc_message'):
73
+ msg = json.loads(response.json_rpc_message)
74
+ else:
75
+ msg = self._protobuf_to_dict(response)
76
+
77
+ await self._response_queue.put(msg)
78
+ except grpc.aio.AioRpcError as e:
79
+ self.logger.error(f"gRPC stream error: {e}")
80
+ except asyncio.CancelledError:
81
+ pass
82
+
83
+ async def send_request(self, method: str, params: Dict = None) -> Dict[str, Any]:
84
+ """Send JSON-RPC request over gRPC."""
85
+ self._request_id += 1
86
+
87
+ request = {
88
+ "jsonrpc": "2.0",
89
+ "id": self._request_id,
90
+ "method": method,
91
+ "params": params or {}
92
+ }
93
+
94
+ # Option 1: Send JSON-RPC as string in protobuf wrapper
95
+ grpc_request = mcp_pb2.MCPRequest(
96
+ json_rpc_message=json.dumps(request)
97
+ )
98
+
99
+ # Option 2: Use native protobuf messages (more efficient)
100
+ # grpc_request = self._dict_to_protobuf(request)
101
+
102
+ await self._stream.write(grpc_request)
103
+
104
+ # Wait for response with matching ID
105
+ while True:
106
+ response = await asyncio.wait_for(
107
+ self._response_queue.get(),
108
+ timeout=self.config.timeout
109
+ )
110
+ if response.get("id") == self._request_id:
111
+ return response
112
+ # Put back if not matching (notifications, etc.)
113
+ await self._response_queue.put(response)
114
+
115
+ async def _initialize_session(self):
116
+ """Initialize MCP session."""
117
+ response = await self.send_request("initialize", {
118
+ "protocolVersion": "2024-11-05",
119
+ "capabilities": {},
120
+ "clientInfo": {
121
+ "name": "ai-parrot",
122
+ "version": "1.0.0"
123
+ }
124
+ })
125
+
126
+ if "error" in response:
127
+ raise MCPConnectionError(f"Initialize failed: {response['error']}")
128
+
129
+ # Send initialized notification
130
+ await self._stream.write(mcp_pb2.MCPRequest(
131
+ json_rpc_message=json.dumps({
132
+ "jsonrpc": "2.0",
133
+ "method": "notifications/initialized"
134
+ })
135
+ ))
136
+
137
+ async def list_tools(self) -> list:
138
+ """List available tools."""
139
+ response = await self.send_request("tools/list")
140
+ return response.get("result", {}).get("tools", [])
141
+
142
+ async def call_tool(self, name: str, arguments: Dict) -> Dict:
143
+ """Call a tool."""
144
+ response = await self.send_request("tools/call", {
145
+ "name": name,
146
+ "arguments": arguments
147
+ })
148
+ return response.get("result", {})
149
+
150
+ async def disconnect(self):
151
+ """Disconnect from gRPC server."""
152
+ self._initialized = False
153
+
154
+ if self._stream_task and not self._stream_task.done():
155
+ self._stream_task.cancel()
156
+ with contextlib.suppress(asyncio.CancelledError):
157
+ await self._stream_task
158
+
159
+ if self._channel:
160
+ await self._channel.close()
161
+ self._channel = None
162
+
163
+ self.logger.info("gRPC disconnected")