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.
- agentui/.prettierrc +15 -0
- agentui/QUICKSTART.md +272 -0
- agentui/README.md +59 -0
- agentui/env.example +16 -0
- agentui/jsconfig.json +14 -0
- agentui/package-lock.json +4242 -0
- agentui/package.json +34 -0
- agentui/scripts/postinstall/apply-patches.mjs +260 -0
- agentui/src/app.css +61 -0
- agentui/src/app.d.ts +13 -0
- agentui/src/app.html +12 -0
- agentui/src/components/LoadingSpinner.svelte +64 -0
- agentui/src/components/ThemeSwitcher.svelte +159 -0
- agentui/src/components/index.js +4 -0
- agentui/src/lib/api/bots.ts +60 -0
- agentui/src/lib/api/chat.ts +22 -0
- agentui/src/lib/api/http.ts +25 -0
- agentui/src/lib/components/BotCard.svelte +33 -0
- agentui/src/lib/components/ChatBubble.svelte +63 -0
- agentui/src/lib/components/Toast.svelte +21 -0
- agentui/src/lib/config.ts +20 -0
- agentui/src/lib/stores/auth.svelte.ts +73 -0
- agentui/src/lib/stores/theme.svelte.js +64 -0
- agentui/src/lib/stores/toast.svelte.ts +31 -0
- agentui/src/lib/utils/conversation.ts +39 -0
- agentui/src/routes/+layout.svelte +20 -0
- agentui/src/routes/+page.svelte +232 -0
- agentui/src/routes/login/+page.svelte +200 -0
- agentui/src/routes/talk/[agentId]/+page.svelte +297 -0
- agentui/src/routes/talk/[agentId]/+page.ts +7 -0
- agentui/static/README.md +1 -0
- agentui/svelte.config.js +11 -0
- agentui/tailwind.config.ts +53 -0
- agentui/tsconfig.json +3 -0
- agentui/vite.config.ts +10 -0
- ai_parrot-0.17.2.dist-info/METADATA +472 -0
- ai_parrot-0.17.2.dist-info/RECORD +535 -0
- ai_parrot-0.17.2.dist-info/WHEEL +6 -0
- ai_parrot-0.17.2.dist-info/entry_points.txt +2 -0
- ai_parrot-0.17.2.dist-info/licenses/LICENSE +21 -0
- ai_parrot-0.17.2.dist-info/top_level.txt +6 -0
- crew-builder/.prettierrc +15 -0
- crew-builder/QUICKSTART.md +259 -0
- crew-builder/README.md +113 -0
- crew-builder/env.example +17 -0
- crew-builder/jsconfig.json +14 -0
- crew-builder/package-lock.json +4182 -0
- crew-builder/package.json +37 -0
- crew-builder/scripts/postinstall/apply-patches.mjs +260 -0
- crew-builder/src/app.css +62 -0
- crew-builder/src/app.d.ts +13 -0
- crew-builder/src/app.html +12 -0
- crew-builder/src/components/LoadingSpinner.svelte +64 -0
- crew-builder/src/components/ThemeSwitcher.svelte +149 -0
- crew-builder/src/components/index.js +9 -0
- crew-builder/src/lib/api/bots.ts +60 -0
- crew-builder/src/lib/api/chat.ts +80 -0
- crew-builder/src/lib/api/client.ts +56 -0
- crew-builder/src/lib/api/crew/crew.ts +136 -0
- crew-builder/src/lib/api/index.ts +5 -0
- crew-builder/src/lib/api/o365/auth.ts +65 -0
- crew-builder/src/lib/auth/auth.ts +54 -0
- crew-builder/src/lib/components/AgentNode.svelte +43 -0
- crew-builder/src/lib/components/BotCard.svelte +33 -0
- crew-builder/src/lib/components/ChatBubble.svelte +67 -0
- crew-builder/src/lib/components/ConfigPanel.svelte +278 -0
- crew-builder/src/lib/components/JsonTreeNode.svelte +76 -0
- crew-builder/src/lib/components/JsonViewer.svelte +24 -0
- crew-builder/src/lib/components/MarkdownEditor.svelte +48 -0
- crew-builder/src/lib/components/ThemeToggle.svelte +36 -0
- crew-builder/src/lib/components/Toast.svelte +67 -0
- crew-builder/src/lib/components/Toolbar.svelte +157 -0
- crew-builder/src/lib/components/index.ts +10 -0
- crew-builder/src/lib/config.ts +8 -0
- crew-builder/src/lib/stores/auth.svelte.ts +228 -0
- crew-builder/src/lib/stores/crewStore.ts +369 -0
- crew-builder/src/lib/stores/theme.svelte.js +145 -0
- crew-builder/src/lib/stores/toast.svelte.ts +69 -0
- crew-builder/src/lib/utils/conversation.ts +39 -0
- crew-builder/src/lib/utils/markdown.ts +122 -0
- crew-builder/src/lib/utils/talkHistory.ts +47 -0
- crew-builder/src/routes/+layout.svelte +20 -0
- crew-builder/src/routes/+page.svelte +539 -0
- crew-builder/src/routes/agents/+page.svelte +247 -0
- crew-builder/src/routes/agents/[agentId]/+page.svelte +288 -0
- crew-builder/src/routes/agents/[agentId]/+page.ts +7 -0
- crew-builder/src/routes/builder/+page.svelte +204 -0
- crew-builder/src/routes/crew/ask/+page.svelte +1052 -0
- crew-builder/src/routes/crew/ask/+page.ts +1 -0
- crew-builder/src/routes/integrations/o365/+page.svelte +304 -0
- crew-builder/src/routes/login/+page.svelte +197 -0
- crew-builder/src/routes/talk/[agentId]/+page.svelte +487 -0
- crew-builder/src/routes/talk/[agentId]/+page.ts +7 -0
- crew-builder/static/README.md +1 -0
- crew-builder/svelte.config.js +11 -0
- crew-builder/tailwind.config.ts +53 -0
- crew-builder/tsconfig.json +3 -0
- crew-builder/vite.config.ts +10 -0
- mcp_servers/calculator_server.py +309 -0
- parrot/__init__.py +27 -0
- parrot/__pycache__/__init__.cpython-310.pyc +0 -0
- parrot/__pycache__/version.cpython-310.pyc +0 -0
- parrot/_version.py +34 -0
- parrot/a2a/__init__.py +48 -0
- parrot/a2a/client.py +658 -0
- parrot/a2a/discovery.py +89 -0
- parrot/a2a/mixin.py +257 -0
- parrot/a2a/models.py +376 -0
- parrot/a2a/server.py +770 -0
- parrot/agents/__init__.py +29 -0
- parrot/bots/__init__.py +12 -0
- parrot/bots/a2a_agent.py +19 -0
- parrot/bots/abstract.py +3139 -0
- parrot/bots/agent.py +1129 -0
- parrot/bots/basic.py +9 -0
- parrot/bots/chatbot.py +669 -0
- parrot/bots/data.py +1618 -0
- parrot/bots/database/__init__.py +5 -0
- parrot/bots/database/abstract.py +3071 -0
- parrot/bots/database/cache.py +286 -0
- parrot/bots/database/models.py +468 -0
- parrot/bots/database/prompts.py +154 -0
- parrot/bots/database/retries.py +98 -0
- parrot/bots/database/router.py +269 -0
- parrot/bots/database/sql.py +41 -0
- parrot/bots/db/__init__.py +6 -0
- parrot/bots/db/abstract.py +556 -0
- parrot/bots/db/bigquery.py +602 -0
- parrot/bots/db/cache.py +85 -0
- parrot/bots/db/documentdb.py +668 -0
- parrot/bots/db/elastic.py +1014 -0
- parrot/bots/db/influx.py +898 -0
- parrot/bots/db/mock.py +96 -0
- parrot/bots/db/multi.py +783 -0
- parrot/bots/db/prompts.py +185 -0
- parrot/bots/db/sql.py +1255 -0
- parrot/bots/db/tools.py +212 -0
- parrot/bots/document.py +680 -0
- parrot/bots/hrbot.py +15 -0
- parrot/bots/kb.py +170 -0
- parrot/bots/mcp.py +36 -0
- parrot/bots/orchestration/README.md +463 -0
- parrot/bots/orchestration/__init__.py +1 -0
- parrot/bots/orchestration/agent.py +155 -0
- parrot/bots/orchestration/crew.py +3330 -0
- parrot/bots/orchestration/fsm.py +1179 -0
- parrot/bots/orchestration/hr.py +434 -0
- parrot/bots/orchestration/storage/__init__.py +4 -0
- parrot/bots/orchestration/storage/memory.py +100 -0
- parrot/bots/orchestration/storage/mixin.py +119 -0
- parrot/bots/orchestration/verify.py +202 -0
- parrot/bots/product.py +204 -0
- parrot/bots/prompts/__init__.py +96 -0
- parrot/bots/prompts/agents.py +155 -0
- parrot/bots/prompts/data.py +216 -0
- parrot/bots/prompts/output_generation.py +8 -0
- parrot/bots/scraper/__init__.py +3 -0
- parrot/bots/scraper/models.py +122 -0
- parrot/bots/scraper/scraper.py +1173 -0
- parrot/bots/scraper/templates.py +115 -0
- parrot/bots/stores/__init__.py +5 -0
- parrot/bots/stores/local.py +172 -0
- parrot/bots/webdev.py +81 -0
- parrot/cli.py +17 -0
- parrot/clients/__init__.py +16 -0
- parrot/clients/base.py +1491 -0
- parrot/clients/claude.py +1191 -0
- parrot/clients/factory.py +129 -0
- parrot/clients/google.py +4567 -0
- parrot/clients/gpt.py +1975 -0
- parrot/clients/grok.py +432 -0
- parrot/clients/groq.py +986 -0
- parrot/clients/hf.py +582 -0
- parrot/clients/models.py +18 -0
- parrot/conf.py +395 -0
- parrot/embeddings/__init__.py +9 -0
- parrot/embeddings/base.py +157 -0
- parrot/embeddings/google.py +98 -0
- parrot/embeddings/huggingface.py +74 -0
- parrot/embeddings/openai.py +84 -0
- parrot/embeddings/processor.py +88 -0
- parrot/exceptions.c +13868 -0
- parrot/exceptions.cpython-310-x86_64-linux-gnu.so +0 -0
- parrot/exceptions.pxd +22 -0
- parrot/exceptions.pxi +15 -0
- parrot/exceptions.pyx +44 -0
- parrot/generators/__init__.py +29 -0
- parrot/generators/base.py +200 -0
- parrot/generators/html.py +293 -0
- parrot/generators/react.py +205 -0
- parrot/generators/streamlit.py +203 -0
- parrot/generators/template.py +105 -0
- parrot/handlers/__init__.py +4 -0
- parrot/handlers/agent.py +861 -0
- parrot/handlers/agents/__init__.py +1 -0
- parrot/handlers/agents/abstract.py +900 -0
- parrot/handlers/bots.py +338 -0
- parrot/handlers/chat.py +915 -0
- parrot/handlers/creation.sql +192 -0
- parrot/handlers/crew/ARCHITECTURE.md +362 -0
- parrot/handlers/crew/README_BOTMANAGER_PERSISTENCE.md +303 -0
- parrot/handlers/crew/README_REDIS_PERSISTENCE.md +366 -0
- parrot/handlers/crew/__init__.py +0 -0
- parrot/handlers/crew/handler.py +801 -0
- parrot/handlers/crew/models.py +229 -0
- parrot/handlers/crew/redis_persistence.py +523 -0
- parrot/handlers/jobs/__init__.py +10 -0
- parrot/handlers/jobs/job.py +384 -0
- parrot/handlers/jobs/mixin.py +627 -0
- parrot/handlers/jobs/models.py +115 -0
- parrot/handlers/jobs/worker.py +31 -0
- parrot/handlers/models.py +596 -0
- parrot/handlers/o365_auth.py +105 -0
- parrot/handlers/stream.py +337 -0
- parrot/interfaces/__init__.py +6 -0
- parrot/interfaces/aws.py +143 -0
- parrot/interfaces/credentials.py +113 -0
- parrot/interfaces/database.py +27 -0
- parrot/interfaces/google.py +1123 -0
- parrot/interfaces/hierarchy.py +1227 -0
- parrot/interfaces/http.py +651 -0
- parrot/interfaces/images/__init__.py +0 -0
- parrot/interfaces/images/plugins/__init__.py +24 -0
- parrot/interfaces/images/plugins/abstract.py +58 -0
- parrot/interfaces/images/plugins/analisys.py +148 -0
- parrot/interfaces/images/plugins/classify.py +150 -0
- parrot/interfaces/images/plugins/classifybase.py +182 -0
- parrot/interfaces/images/plugins/detect.py +150 -0
- parrot/interfaces/images/plugins/exif.py +1103 -0
- parrot/interfaces/images/plugins/hash.py +52 -0
- parrot/interfaces/images/plugins/vision.py +104 -0
- parrot/interfaces/images/plugins/yolo.py +66 -0
- parrot/interfaces/images/plugins/zerodetect.py +197 -0
- parrot/interfaces/o365.py +978 -0
- parrot/interfaces/onedrive.py +822 -0
- parrot/interfaces/sharepoint.py +1435 -0
- parrot/interfaces/soap.py +257 -0
- parrot/loaders/__init__.py +8 -0
- parrot/loaders/abstract.py +1131 -0
- parrot/loaders/audio.py +199 -0
- parrot/loaders/basepdf.py +53 -0
- parrot/loaders/basevideo.py +1568 -0
- parrot/loaders/csv.py +409 -0
- parrot/loaders/docx.py +116 -0
- parrot/loaders/epubloader.py +316 -0
- parrot/loaders/excel.py +199 -0
- parrot/loaders/factory.py +55 -0
- parrot/loaders/files/__init__.py +0 -0
- parrot/loaders/files/abstract.py +39 -0
- parrot/loaders/files/html.py +26 -0
- parrot/loaders/files/text.py +63 -0
- parrot/loaders/html.py +152 -0
- parrot/loaders/markdown.py +442 -0
- parrot/loaders/pdf.py +373 -0
- parrot/loaders/pdfmark.py +320 -0
- parrot/loaders/pdftables.py +506 -0
- parrot/loaders/ppt.py +476 -0
- parrot/loaders/qa.py +63 -0
- parrot/loaders/splitters/__init__.py +10 -0
- parrot/loaders/splitters/base.py +138 -0
- parrot/loaders/splitters/md.py +228 -0
- parrot/loaders/splitters/token.py +143 -0
- parrot/loaders/txt.py +26 -0
- parrot/loaders/video.py +89 -0
- parrot/loaders/videolocal.py +218 -0
- parrot/loaders/videounderstanding.py +377 -0
- parrot/loaders/vimeo.py +167 -0
- parrot/loaders/web.py +599 -0
- parrot/loaders/youtube.py +504 -0
- parrot/manager/__init__.py +5 -0
- parrot/manager/manager.py +1030 -0
- parrot/mcp/__init__.py +28 -0
- parrot/mcp/adapter.py +105 -0
- parrot/mcp/cli.py +174 -0
- parrot/mcp/client.py +119 -0
- parrot/mcp/config.py +75 -0
- parrot/mcp/integration.py +842 -0
- parrot/mcp/oauth.py +933 -0
- parrot/mcp/server.py +225 -0
- parrot/mcp/transports/__init__.py +3 -0
- parrot/mcp/transports/base.py +279 -0
- parrot/mcp/transports/grpc_session.py +163 -0
- parrot/mcp/transports/http.py +312 -0
- parrot/mcp/transports/mcp.proto +108 -0
- parrot/mcp/transports/quic.py +1082 -0
- parrot/mcp/transports/sse.py +330 -0
- parrot/mcp/transports/stdio.py +309 -0
- parrot/mcp/transports/unix.py +395 -0
- parrot/mcp/transports/websocket.py +547 -0
- parrot/memory/__init__.py +16 -0
- parrot/memory/abstract.py +209 -0
- parrot/memory/agent.py +32 -0
- parrot/memory/cache.py +175 -0
- parrot/memory/core.py +555 -0
- parrot/memory/file.py +153 -0
- parrot/memory/mem.py +131 -0
- parrot/memory/redis.py +613 -0
- parrot/models/__init__.py +46 -0
- parrot/models/basic.py +118 -0
- parrot/models/compliance.py +208 -0
- parrot/models/crew.py +395 -0
- parrot/models/detections.py +654 -0
- parrot/models/generation.py +85 -0
- parrot/models/google.py +223 -0
- parrot/models/groq.py +23 -0
- parrot/models/openai.py +30 -0
- parrot/models/outputs.py +285 -0
- parrot/models/responses.py +938 -0
- parrot/notifications/__init__.py +743 -0
- parrot/openapi/__init__.py +3 -0
- parrot/openapi/components.yaml +641 -0
- parrot/openapi/config.py +322 -0
- parrot/outputs/__init__.py +32 -0
- parrot/outputs/formats/__init__.py +108 -0
- parrot/outputs/formats/altair.py +359 -0
- parrot/outputs/formats/application.py +122 -0
- parrot/outputs/formats/base.py +351 -0
- parrot/outputs/formats/bokeh.py +356 -0
- parrot/outputs/formats/card.py +424 -0
- parrot/outputs/formats/chart.py +436 -0
- parrot/outputs/formats/d3.py +255 -0
- parrot/outputs/formats/echarts.py +310 -0
- parrot/outputs/formats/generators/__init__.py +0 -0
- parrot/outputs/formats/generators/abstract.py +61 -0
- parrot/outputs/formats/generators/panel.py +145 -0
- parrot/outputs/formats/generators/streamlit.py +86 -0
- parrot/outputs/formats/generators/terminal.py +63 -0
- parrot/outputs/formats/holoviews.py +310 -0
- parrot/outputs/formats/html.py +147 -0
- parrot/outputs/formats/jinja2.py +46 -0
- parrot/outputs/formats/json.py +87 -0
- parrot/outputs/formats/map.py +933 -0
- parrot/outputs/formats/markdown.py +172 -0
- parrot/outputs/formats/matplotlib.py +237 -0
- parrot/outputs/formats/mixins/__init__.py +0 -0
- parrot/outputs/formats/mixins/emaps.py +855 -0
- parrot/outputs/formats/plotly.py +341 -0
- parrot/outputs/formats/seaborn.py +310 -0
- parrot/outputs/formats/table.py +397 -0
- parrot/outputs/formats/template_report.py +138 -0
- parrot/outputs/formats/yaml.py +125 -0
- parrot/outputs/formatter.py +152 -0
- parrot/outputs/templates/__init__.py +95 -0
- parrot/pipelines/__init__.py +0 -0
- parrot/pipelines/abstract.py +210 -0
- parrot/pipelines/detector.py +124 -0
- parrot/pipelines/models.py +90 -0
- parrot/pipelines/planogram.py +3002 -0
- parrot/pipelines/table.sql +97 -0
- parrot/plugins/__init__.py +106 -0
- parrot/plugins/importer.py +80 -0
- parrot/py.typed +0 -0
- parrot/registry/__init__.py +18 -0
- parrot/registry/registry.py +594 -0
- parrot/scheduler/__init__.py +1189 -0
- parrot/scheduler/models.py +60 -0
- parrot/security/__init__.py +16 -0
- parrot/security/prompt_injection.py +268 -0
- parrot/security/security_events.sql +25 -0
- parrot/services/__init__.py +1 -0
- parrot/services/mcp/__init__.py +8 -0
- parrot/services/mcp/config.py +13 -0
- parrot/services/mcp/server.py +295 -0
- parrot/services/o365_remote_auth.py +235 -0
- parrot/stores/__init__.py +7 -0
- parrot/stores/abstract.py +352 -0
- parrot/stores/arango.py +1090 -0
- parrot/stores/bigquery.py +1377 -0
- parrot/stores/cache.py +106 -0
- parrot/stores/empty.py +10 -0
- parrot/stores/faiss_store.py +1157 -0
- parrot/stores/kb/__init__.py +9 -0
- parrot/stores/kb/abstract.py +68 -0
- parrot/stores/kb/cache.py +165 -0
- parrot/stores/kb/doc.py +325 -0
- parrot/stores/kb/hierarchy.py +346 -0
- parrot/stores/kb/local.py +457 -0
- parrot/stores/kb/prompt.py +28 -0
- parrot/stores/kb/redis.py +659 -0
- parrot/stores/kb/store.py +115 -0
- parrot/stores/kb/user.py +374 -0
- parrot/stores/models.py +59 -0
- parrot/stores/pgvector.py +3 -0
- parrot/stores/postgres.py +2853 -0
- parrot/stores/utils/__init__.py +0 -0
- parrot/stores/utils/chunking.py +197 -0
- parrot/telemetry/__init__.py +3 -0
- parrot/telemetry/mixin.py +111 -0
- parrot/template/__init__.py +3 -0
- parrot/template/engine.py +259 -0
- parrot/tools/__init__.py +23 -0
- parrot/tools/abstract.py +644 -0
- parrot/tools/agent.py +363 -0
- parrot/tools/arangodbsearch.py +537 -0
- parrot/tools/arxiv_tool.py +188 -0
- parrot/tools/calculator/__init__.py +3 -0
- parrot/tools/calculator/operations/__init__.py +38 -0
- parrot/tools/calculator/operations/calculus.py +80 -0
- parrot/tools/calculator/operations/statistics.py +76 -0
- parrot/tools/calculator/tool.py +150 -0
- parrot/tools/cloudwatch.py +988 -0
- parrot/tools/codeinterpreter/__init__.py +127 -0
- parrot/tools/codeinterpreter/executor.py +371 -0
- parrot/tools/codeinterpreter/internals.py +473 -0
- parrot/tools/codeinterpreter/models.py +643 -0
- parrot/tools/codeinterpreter/prompts.py +224 -0
- parrot/tools/codeinterpreter/tool.py +664 -0
- parrot/tools/company_info/__init__.py +6 -0
- parrot/tools/company_info/tool.py +1138 -0
- parrot/tools/correlationanalysis.py +437 -0
- parrot/tools/database/abstract.py +286 -0
- parrot/tools/database/bq.py +115 -0
- parrot/tools/database/cache.py +284 -0
- parrot/tools/database/models.py +95 -0
- parrot/tools/database/pg.py +343 -0
- parrot/tools/databasequery.py +1159 -0
- parrot/tools/db.py +1800 -0
- parrot/tools/ddgo.py +370 -0
- parrot/tools/decorators.py +271 -0
- parrot/tools/dftohtml.py +282 -0
- parrot/tools/document.py +549 -0
- parrot/tools/ecs.py +819 -0
- parrot/tools/edareport.py +368 -0
- parrot/tools/elasticsearch.py +1049 -0
- parrot/tools/employees.py +462 -0
- parrot/tools/epson/__init__.py +96 -0
- parrot/tools/excel.py +683 -0
- parrot/tools/file/__init__.py +13 -0
- parrot/tools/file/abstract.py +76 -0
- parrot/tools/file/gcs.py +378 -0
- parrot/tools/file/local.py +284 -0
- parrot/tools/file/s3.py +511 -0
- parrot/tools/file/tmp.py +309 -0
- parrot/tools/file/tool.py +501 -0
- parrot/tools/file_reader.py +129 -0
- parrot/tools/flowtask/__init__.py +19 -0
- parrot/tools/flowtask/tool.py +761 -0
- parrot/tools/gittoolkit.py +508 -0
- parrot/tools/google/__init__.py +18 -0
- parrot/tools/google/base.py +169 -0
- parrot/tools/google/tools.py +1251 -0
- parrot/tools/googlelocation.py +5 -0
- parrot/tools/googleroutes.py +5 -0
- parrot/tools/googlesearch.py +5 -0
- parrot/tools/googlesitesearch.py +5 -0
- parrot/tools/googlevoice.py +2 -0
- parrot/tools/gvoice.py +695 -0
- parrot/tools/ibisworld/README.md +225 -0
- parrot/tools/ibisworld/__init__.py +11 -0
- parrot/tools/ibisworld/tool.py +366 -0
- parrot/tools/jiratoolkit.py +1718 -0
- parrot/tools/manager.py +1098 -0
- parrot/tools/math.py +152 -0
- parrot/tools/metadata.py +476 -0
- parrot/tools/msteams.py +1621 -0
- parrot/tools/msword.py +635 -0
- parrot/tools/multidb.py +580 -0
- parrot/tools/multistoresearch.py +369 -0
- parrot/tools/networkninja.py +167 -0
- parrot/tools/nextstop/__init__.py +4 -0
- parrot/tools/nextstop/base.py +286 -0
- parrot/tools/nextstop/employee.py +733 -0
- parrot/tools/nextstop/store.py +462 -0
- parrot/tools/notification.py +435 -0
- parrot/tools/o365/__init__.py +42 -0
- parrot/tools/o365/base.py +295 -0
- parrot/tools/o365/bundle.py +522 -0
- parrot/tools/o365/events.py +554 -0
- parrot/tools/o365/mail.py +992 -0
- parrot/tools/o365/onedrive.py +497 -0
- parrot/tools/o365/sharepoint.py +641 -0
- parrot/tools/openapi_toolkit.py +904 -0
- parrot/tools/openweather.py +527 -0
- parrot/tools/pdfprint.py +1001 -0
- parrot/tools/powerbi.py +518 -0
- parrot/tools/powerpoint.py +1113 -0
- parrot/tools/pricestool.py +146 -0
- parrot/tools/products/__init__.py +246 -0
- parrot/tools/prophet_tool.py +171 -0
- parrot/tools/pythonpandas.py +630 -0
- parrot/tools/pythonrepl.py +910 -0
- parrot/tools/qsource.py +436 -0
- parrot/tools/querytoolkit.py +395 -0
- parrot/tools/quickeda.py +827 -0
- parrot/tools/resttool.py +553 -0
- parrot/tools/retail/__init__.py +0 -0
- parrot/tools/retail/bby.py +528 -0
- parrot/tools/sandboxtool.py +703 -0
- parrot/tools/sassie/__init__.py +352 -0
- parrot/tools/scraping/__init__.py +7 -0
- parrot/tools/scraping/docs/select.md +466 -0
- parrot/tools/scraping/documentation.md +1278 -0
- parrot/tools/scraping/driver.py +436 -0
- parrot/tools/scraping/models.py +576 -0
- parrot/tools/scraping/options.py +85 -0
- parrot/tools/scraping/orchestrator.py +517 -0
- parrot/tools/scraping/readme.md +740 -0
- parrot/tools/scraping/tool.py +3115 -0
- parrot/tools/seasonaldetection.py +642 -0
- parrot/tools/shell_tool/__init__.py +5 -0
- parrot/tools/shell_tool/actions.py +408 -0
- parrot/tools/shell_tool/engine.py +155 -0
- parrot/tools/shell_tool/models.py +322 -0
- parrot/tools/shell_tool/tool.py +442 -0
- parrot/tools/site_search.py +214 -0
- parrot/tools/textfile.py +418 -0
- parrot/tools/think.py +378 -0
- parrot/tools/toolkit.py +298 -0
- parrot/tools/webapp_tool.py +187 -0
- parrot/tools/whatif.py +1279 -0
- parrot/tools/workday/MULTI_WSDL_EXAMPLE.md +249 -0
- parrot/tools/workday/__init__.py +6 -0
- parrot/tools/workday/models.py +1389 -0
- parrot/tools/workday/tool.py +1293 -0
- parrot/tools/yfinance_tool.py +306 -0
- parrot/tools/zipcode.py +217 -0
- parrot/utils/__init__.py +2 -0
- parrot/utils/helpers.py +73 -0
- parrot/utils/parsers/__init__.py +5 -0
- parrot/utils/parsers/toml.c +12078 -0
- parrot/utils/parsers/toml.cpython-310-x86_64-linux-gnu.so +0 -0
- parrot/utils/parsers/toml.pyx +21 -0
- parrot/utils/toml.py +11 -0
- parrot/utils/types.cpp +20936 -0
- parrot/utils/types.cpython-310-x86_64-linux-gnu.so +0 -0
- parrot/utils/types.pyx +213 -0
- parrot/utils/uv.py +11 -0
- parrot/version.py +10 -0
- parrot/yaml-rs/Cargo.lock +350 -0
- parrot/yaml-rs/Cargo.toml +19 -0
- parrot/yaml-rs/pyproject.toml +19 -0
- parrot/yaml-rs/python/yaml_rs/__init__.py +81 -0
- parrot/yaml-rs/src/lib.rs +222 -0
- requirements/docker-compose.yml +24 -0
- 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
|