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,547 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import json
|
|
3
|
+
import logging
|
|
4
|
+
import uuid
|
|
5
|
+
import contextlib
|
|
6
|
+
from typing import Dict, Any, Optional
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from aiohttp import web, WSMsgType, ClientSession, ClientWebSocketResponse
|
|
9
|
+
import aiohttp
|
|
10
|
+
|
|
11
|
+
from parrot.mcp.config import MCPServerConfig
|
|
12
|
+
from parrot.mcp.transports.base import MCPServerBase
|
|
13
|
+
from parrot.mcp.oauth import OAuthRoutesMixin
|
|
14
|
+
from parrot.mcp.client import MCPClientConfig, MCPConnectionError
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class WebSocketConnection:
|
|
19
|
+
"""Represents an active WebSocket connection with session info."""
|
|
20
|
+
websocket: web.WebSocketResponse
|
|
21
|
+
session_id: str
|
|
22
|
+
message_queue: asyncio.Queue
|
|
23
|
+
last_ping: float = 0.0
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class WebSocketMCPServer(OAuthRoutesMixin, MCPServerBase):
|
|
27
|
+
"""MCP server using WebSocket transport for bidirectional communication.
|
|
28
|
+
|
|
29
|
+
Implements the WebSocket transport as proposed in SEP-1288:
|
|
30
|
+
- Session-based connection management (single connection per session)
|
|
31
|
+
- Bidirectional JSON-RPC communication
|
|
32
|
+
- Server-initiated notifications support
|
|
33
|
+
- OAuth authentication via query params or websocket subprotocol
|
|
34
|
+
- Automatic ping/pong keep-alive
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(self, config: MCPServerConfig, parent_app: Optional[web.Application] = None):
|
|
38
|
+
super().__init__(config)
|
|
39
|
+
self.app = parent_app or web.Application()
|
|
40
|
+
self.base_path = config.base_path or "/mcp"
|
|
41
|
+
# WebSocket endpoint is at /mcp/ws (base_path + /ws)
|
|
42
|
+
self.ws_path = f"{self.base_path.rstrip('/')}/ws"
|
|
43
|
+
self._init_oauth_support()
|
|
44
|
+
self.runner = None
|
|
45
|
+
self.site = None
|
|
46
|
+
self.sessions: Dict[str, WebSocketConnection] = {}
|
|
47
|
+
self._external_setup = parent_app is not None
|
|
48
|
+
|
|
49
|
+
# Register routes
|
|
50
|
+
self.app.router.add_get(self.ws_path, self._handle_websocket)
|
|
51
|
+
self.app.router.add_get("/", self._handle_info, allow_head=True)
|
|
52
|
+
|
|
53
|
+
if self.oauth_server:
|
|
54
|
+
self.oauth_server.register_routes(self.app)
|
|
55
|
+
|
|
56
|
+
async def start(self):
|
|
57
|
+
"""Start the WebSocket MCP server."""
|
|
58
|
+
if self._external_setup:
|
|
59
|
+
self.logger.info("WebSocket MCP server using existing aiohttp application")
|
|
60
|
+
return
|
|
61
|
+
|
|
62
|
+
self.logger.info(
|
|
63
|
+
f"Starting WebSocket MCP server on {self.config.host}:{self.config.port}"
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
self.runner = web.AppRunner(self.app)
|
|
67
|
+
await self.runner.setup()
|
|
68
|
+
|
|
69
|
+
self.site = web.TCPSite(
|
|
70
|
+
self.runner,
|
|
71
|
+
self.config.host,
|
|
72
|
+
self.config.port
|
|
73
|
+
)
|
|
74
|
+
await self.site.start()
|
|
75
|
+
|
|
76
|
+
self.logger.info(
|
|
77
|
+
f"WebSocket MCP server started at ws://{self.config.host}:{self.config.port}{self.ws_path}"
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
async def stop(self):
|
|
81
|
+
"""Stop the WebSocket server."""
|
|
82
|
+
# Close all active WebSocket connections
|
|
83
|
+
for session_id, conn in list(self.sessions.items()):
|
|
84
|
+
try:
|
|
85
|
+
await conn.websocket.close()
|
|
86
|
+
except Exception as e:
|
|
87
|
+
self.logger.warning(f"Error closing WebSocket for session {session_id}: {e}")
|
|
88
|
+
self.sessions.pop(session_id, None)
|
|
89
|
+
|
|
90
|
+
if not self._external_setup:
|
|
91
|
+
if self.site:
|
|
92
|
+
await self.site.stop()
|
|
93
|
+
if self.runner:
|
|
94
|
+
await self.runner.cleanup()
|
|
95
|
+
|
|
96
|
+
self.logger.info("WebSocket MCP server stopped")
|
|
97
|
+
|
|
98
|
+
async def _handle_info(self, request: web.Request) -> web.Response:
|
|
99
|
+
"""Return server info endpoint."""
|
|
100
|
+
auth_response = self._authenticate_request(request)
|
|
101
|
+
if auth_response:
|
|
102
|
+
return auth_response
|
|
103
|
+
|
|
104
|
+
info = {
|
|
105
|
+
"name": self.config.name,
|
|
106
|
+
"version": self.config.version,
|
|
107
|
+
"description": self.config.description,
|
|
108
|
+
"transport": "websocket",
|
|
109
|
+
"endpoint": self.ws_path,
|
|
110
|
+
"tools": list(self.tools.keys()),
|
|
111
|
+
"tool_count": len(self.tools)
|
|
112
|
+
}
|
|
113
|
+
return web.json_response(info)
|
|
114
|
+
|
|
115
|
+
def _get_session_id(self, request: web.Request) -> str:
|
|
116
|
+
"""Extract session ID from request headers or query params, or generate new one."""
|
|
117
|
+
session_id = (
|
|
118
|
+
request.headers.get("X-MCP-Session-Id") or
|
|
119
|
+
request.query.get("session_id") or
|
|
120
|
+
str(uuid.uuid4())
|
|
121
|
+
)
|
|
122
|
+
return session_id
|
|
123
|
+
|
|
124
|
+
async def _handle_websocket(self, request: web.Request) -> web.WebSocketResponse:
|
|
125
|
+
"""Handle WebSocket connection upgrade and message loop."""
|
|
126
|
+
# OAuth authentication check
|
|
127
|
+
auth_response = self._authenticate_request(request)
|
|
128
|
+
if auth_response:
|
|
129
|
+
return auth_response
|
|
130
|
+
|
|
131
|
+
session_id = self._get_session_id(request)
|
|
132
|
+
|
|
133
|
+
# Close existing connection for this session (SEP-1288: single connection per session)
|
|
134
|
+
if session_id in self.sessions:
|
|
135
|
+
old_conn = self.sessions[session_id]
|
|
136
|
+
self.logger.info(f"Closing previous WebSocket connection for session {session_id}")
|
|
137
|
+
try:
|
|
138
|
+
await old_conn.websocket.close(code=1000, message=b"New connection established")
|
|
139
|
+
except Exception:
|
|
140
|
+
pass
|
|
141
|
+
|
|
142
|
+
# Upgrade to WebSocket
|
|
143
|
+
ws = web.WebSocketResponse(heartbeat=30.0)
|
|
144
|
+
await ws.prepare(request)
|
|
145
|
+
|
|
146
|
+
# Create connection object
|
|
147
|
+
connection = WebSocketConnection(
|
|
148
|
+
websocket=ws,
|
|
149
|
+
session_id=session_id,
|
|
150
|
+
message_queue=asyncio.Queue()
|
|
151
|
+
)
|
|
152
|
+
self.sessions[session_id] = connection
|
|
153
|
+
|
|
154
|
+
self.logger.info(f"WebSocket client connected: {session_id}")
|
|
155
|
+
|
|
156
|
+
# Send connection established message
|
|
157
|
+
await self._send_message(ws, {
|
|
158
|
+
"jsonrpc": "2.0",
|
|
159
|
+
"method": "notifications/connection",
|
|
160
|
+
"params": {
|
|
161
|
+
"type": "connected",
|
|
162
|
+
"sessionId": session_id
|
|
163
|
+
}
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
try:
|
|
167
|
+
# Message handling loop
|
|
168
|
+
async for msg in ws:
|
|
169
|
+
if msg.type == WSMsgType.TEXT:
|
|
170
|
+
await self._handle_message(session_id, msg.data)
|
|
171
|
+
elif msg.type == WSMsgType.ERROR:
|
|
172
|
+
self.logger.error(f"WebSocket error for {session_id}: {ws.exception()}")
|
|
173
|
+
break
|
|
174
|
+
elif msg.type == WSMsgType.CLOSE:
|
|
175
|
+
self.logger.info(f"WebSocket closed by client: {session_id}")
|
|
176
|
+
break
|
|
177
|
+
|
|
178
|
+
except asyncio.CancelledError:
|
|
179
|
+
self.logger.info(f"WebSocket connection cancelled: {session_id}")
|
|
180
|
+
except Exception as e:
|
|
181
|
+
self.logger.error(f"Error in WebSocket handler for {session_id}: {e}")
|
|
182
|
+
finally:
|
|
183
|
+
# Cleanup
|
|
184
|
+
self.sessions.pop(session_id, None)
|
|
185
|
+
if not ws.closed:
|
|
186
|
+
await ws.close()
|
|
187
|
+
self.logger.info(f"WebSocket client disconnected: {session_id}")
|
|
188
|
+
|
|
189
|
+
return ws
|
|
190
|
+
|
|
191
|
+
async def _handle_message(self, session_id: str, message_data: str):
|
|
192
|
+
"""Handle incoming JSON-RPC message from WebSocket client."""
|
|
193
|
+
try:
|
|
194
|
+
data = json.loads(message_data)
|
|
195
|
+
method = data.get("method")
|
|
196
|
+
params = data.get("params", {})
|
|
197
|
+
request_id = data.get("id")
|
|
198
|
+
|
|
199
|
+
self.logger.debug(f"WebSocket message from {session_id}: {method}")
|
|
200
|
+
|
|
201
|
+
# Handle JSON-RPC request
|
|
202
|
+
try:
|
|
203
|
+
if method == "initialize":
|
|
204
|
+
result = await self.handle_initialize(params)
|
|
205
|
+
elif method == "tools/list":
|
|
206
|
+
result = await self.handle_tools_list(params)
|
|
207
|
+
elif method == "tools/call":
|
|
208
|
+
result = await self.handle_tools_call(params)
|
|
209
|
+
elif method == "notifications/initialized":
|
|
210
|
+
# Client initialization complete notification (no response needed)
|
|
211
|
+
self.logger.info(f"Client {session_id} initialization complete")
|
|
212
|
+
return
|
|
213
|
+
else:
|
|
214
|
+
raise RuntimeError(f"Unknown method: {method}")
|
|
215
|
+
|
|
216
|
+
# Send success response
|
|
217
|
+
response = {
|
|
218
|
+
"jsonrpc": "2.0",
|
|
219
|
+
"id": request_id,
|
|
220
|
+
"result": result
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
except Exception as e:
|
|
224
|
+
self.logger.error(f"Error handling {method} for {session_id}: {e}")
|
|
225
|
+
response = {
|
|
226
|
+
"jsonrpc": "2.0",
|
|
227
|
+
"id": request_id,
|
|
228
|
+
"error": {
|
|
229
|
+
"code": -32603,
|
|
230
|
+
"message": str(e)
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
# Send response back to client
|
|
235
|
+
connection = self.sessions.get(session_id)
|
|
236
|
+
if connection:
|
|
237
|
+
await self._send_message(connection.websocket, response)
|
|
238
|
+
|
|
239
|
+
except json.JSONDecodeError as e:
|
|
240
|
+
self.logger.error(f"Invalid JSON from {session_id}: {e}")
|
|
241
|
+
# Send parse error
|
|
242
|
+
connection = self.sessions.get(session_id)
|
|
243
|
+
if connection:
|
|
244
|
+
await self._send_message(connection.websocket, {
|
|
245
|
+
"jsonrpc": "2.0",
|
|
246
|
+
"id": None,
|
|
247
|
+
"error": {
|
|
248
|
+
"code": -32700,
|
|
249
|
+
"message": "Parse error"
|
|
250
|
+
}
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
async def _send_message(self, ws: web.WebSocketResponse, message: Dict[str, Any]):
|
|
254
|
+
"""Send JSON-RPC message to WebSocket client."""
|
|
255
|
+
try:
|
|
256
|
+
await ws.send_str(json.dumps(message))
|
|
257
|
+
except Exception as e:
|
|
258
|
+
self.logger.error(f"Failed to send WebSocket message: {e}")
|
|
259
|
+
|
|
260
|
+
async def send_notification(self, session_id: str, method: str, params: Dict[str, Any]):
|
|
261
|
+
"""Send server-initiated notification to client.
|
|
262
|
+
|
|
263
|
+
This enables server-to-client notifications like:
|
|
264
|
+
- notifications/resources/updated
|
|
265
|
+
- notifications/tools/list_changed
|
|
266
|
+
"""
|
|
267
|
+
connection = self.sessions.get(session_id)
|
|
268
|
+
if not connection:
|
|
269
|
+
self.logger.warning(f"Cannot send notification: session {session_id} not found")
|
|
270
|
+
return
|
|
271
|
+
|
|
272
|
+
notification = {
|
|
273
|
+
"jsonrpc": "2.0",
|
|
274
|
+
"method": method,
|
|
275
|
+
"params": params
|
|
276
|
+
}
|
|
277
|
+
await self._send_message(connection.websocket, notification)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
class WebSocketMCPSession:
|
|
281
|
+
"""MCP client session for WebSocket transport.
|
|
282
|
+
|
|
283
|
+
Implements the client side of SEP-1288 WebSocket transport:
|
|
284
|
+
- Connects to WebSocket MCP server
|
|
285
|
+
- Manages session ID persistence
|
|
286
|
+
- Handles request/response matching
|
|
287
|
+
- Supports automatic reconnection
|
|
288
|
+
- Receives server-initiated notifications
|
|
289
|
+
"""
|
|
290
|
+
|
|
291
|
+
def __init__(self, config: MCPClientConfig, logger):
|
|
292
|
+
self.config = config
|
|
293
|
+
self.logger = logger
|
|
294
|
+
self._request_id = 0
|
|
295
|
+
self._session: Optional[ClientSession] = None
|
|
296
|
+
self._websocket: Optional[ClientWebSocketResponse] = None
|
|
297
|
+
self._response_futures: Dict[int, asyncio.Future] = {}
|
|
298
|
+
self._session_id: Optional[str] = None
|
|
299
|
+
self._connected = False
|
|
300
|
+
self._receiver_task: Optional[asyncio.Task] = None
|
|
301
|
+
self._reconnect_attempts = 0
|
|
302
|
+
self._max_reconnect_attempts = 5
|
|
303
|
+
|
|
304
|
+
async def connect(self):
|
|
305
|
+
"""Connect to WebSocket MCP server."""
|
|
306
|
+
if self._connected:
|
|
307
|
+
return
|
|
308
|
+
|
|
309
|
+
try:
|
|
310
|
+
# Create aiohttp session
|
|
311
|
+
self._session = ClientSession()
|
|
312
|
+
|
|
313
|
+
# Build WebSocket URL
|
|
314
|
+
url = self.config.url
|
|
315
|
+
if not url.startswith("ws://") and not url.startswith("wss://"):
|
|
316
|
+
# Convert http(s) to ws(s)
|
|
317
|
+
url = url.replace("http://", "ws://").replace("https://", "wss://")
|
|
318
|
+
|
|
319
|
+
# Add session ID if we have one (for reconnection)
|
|
320
|
+
params = {}
|
|
321
|
+
if self._session_id:
|
|
322
|
+
params["session_id"] = self._session_id
|
|
323
|
+
|
|
324
|
+
# Prepare headers
|
|
325
|
+
headers = dict(self.config.headers or {})
|
|
326
|
+
|
|
327
|
+
# Add authentication
|
|
328
|
+
if self.config.auth_type == "bearer" and self.config.auth_config:
|
|
329
|
+
token = self.config.auth_config.get("token")
|
|
330
|
+
if token:
|
|
331
|
+
headers["Authorization"] = f"Bearer {token}"
|
|
332
|
+
elif self.config.auth_type == "api_key" and self.config.auth_config:
|
|
333
|
+
api_key = self.config.auth_config.get("api_key")
|
|
334
|
+
header_name = self.config.auth_config.get("header_name", "X-API-Key")
|
|
335
|
+
if api_key:
|
|
336
|
+
headers[header_name] = api_key
|
|
337
|
+
|
|
338
|
+
# Connect to WebSocket
|
|
339
|
+
self._websocket = await self._session.ws_connect(
|
|
340
|
+
url,
|
|
341
|
+
params=params,
|
|
342
|
+
headers=headers
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
self.logger.info(f"Connected to WebSocket MCP server: {url}")
|
|
346
|
+
|
|
347
|
+
# Mark as connected BEFORE starting receiver and initialization
|
|
348
|
+
self._connected = True
|
|
349
|
+
|
|
350
|
+
# Start background receiver task
|
|
351
|
+
self._receiver_task = asyncio.create_task(self._receive_messages())
|
|
352
|
+
|
|
353
|
+
# Wait a moment for receiver to be ready
|
|
354
|
+
await asyncio.sleep(0.1)
|
|
355
|
+
|
|
356
|
+
# Initialize MCP session
|
|
357
|
+
await self._initialize_session()
|
|
358
|
+
|
|
359
|
+
self._reconnect_attempts = 0
|
|
360
|
+
|
|
361
|
+
except Exception as e:
|
|
362
|
+
self.logger.error(f"Failed to connect to WebSocket MCP server: {e}")
|
|
363
|
+
self._connected = False
|
|
364
|
+
await self.disconnect()
|
|
365
|
+
raise MCPConnectionError(f"WebSocket connection failed: {e}")
|
|
366
|
+
|
|
367
|
+
async def _receive_messages(self):
|
|
368
|
+
"""Background task to receive messages from server."""
|
|
369
|
+
try:
|
|
370
|
+
async for msg in self._websocket:
|
|
371
|
+
if msg.type == WSMsgType.TEXT:
|
|
372
|
+
await self._handle_message(msg.data)
|
|
373
|
+
elif msg.type == WSMsgType.ERROR:
|
|
374
|
+
self.logger.error(f"WebSocket error: {self._websocket.exception()}")
|
|
375
|
+
break
|
|
376
|
+
elif msg.type == WSMsgType.CLOSE:
|
|
377
|
+
self.logger.info("WebSocket closed by server")
|
|
378
|
+
break
|
|
379
|
+
except asyncio.CancelledError:
|
|
380
|
+
self.logger.debug("Message receiver task cancelled")
|
|
381
|
+
except Exception as e:
|
|
382
|
+
self.logger.error(f"Error in message receiver: {e}")
|
|
383
|
+
finally:
|
|
384
|
+
self._connected = False
|
|
385
|
+
|
|
386
|
+
async def _handle_message(self, message_data: str):
|
|
387
|
+
"""Handle incoming message from server."""
|
|
388
|
+
try:
|
|
389
|
+
data = json.loads(message_data)
|
|
390
|
+
|
|
391
|
+
# Check if this is a response to a request
|
|
392
|
+
if "id" in data and data["id"] is not None:
|
|
393
|
+
request_id = data["id"]
|
|
394
|
+
if request_id in self._response_futures:
|
|
395
|
+
future = self._response_futures.pop(request_id)
|
|
396
|
+
if "error" in data:
|
|
397
|
+
future.set_exception(
|
|
398
|
+
RuntimeError(f"MCP error: {data['error']['message']}")
|
|
399
|
+
)
|
|
400
|
+
else:
|
|
401
|
+
future.set_result(data.get("result"))
|
|
402
|
+
|
|
403
|
+
# Handle notifications from server
|
|
404
|
+
elif "method" in data:
|
|
405
|
+
method = data["method"]
|
|
406
|
+
params = data.get("params", {})
|
|
407
|
+
|
|
408
|
+
if method == "notifications/connection":
|
|
409
|
+
# Extract session ID from connection notification
|
|
410
|
+
self._session_id = params.get("sessionId")
|
|
411
|
+
self.logger.info(f"Session ID: {self._session_id}")
|
|
412
|
+
else:
|
|
413
|
+
self.logger.info(f"Server notification: {method}")
|
|
414
|
+
# Could add notification handlers here
|
|
415
|
+
|
|
416
|
+
except json.JSONDecodeError as e:
|
|
417
|
+
self.logger.error(f"Invalid JSON from server: {e}")
|
|
418
|
+
except Exception as e:
|
|
419
|
+
self.logger.error(f"Error handling message: {e}")
|
|
420
|
+
|
|
421
|
+
async def _initialize_session(self):
|
|
422
|
+
"""Initialize MCP session over WebSocket."""
|
|
423
|
+
init_result = await self._send_request("initialize", {
|
|
424
|
+
"protocolVersion": "2024-11-05",
|
|
425
|
+
"capabilities": {},
|
|
426
|
+
"clientInfo": {
|
|
427
|
+
"name": "ai-parrot-websocket-client",
|
|
428
|
+
"version": "1.0"
|
|
429
|
+
}
|
|
430
|
+
})
|
|
431
|
+
|
|
432
|
+
self.logger.info(f"MCP session initialized: {init_result.get('serverInfo', {}).get('name', 'unknown')}")
|
|
433
|
+
|
|
434
|
+
# Send initialized notification
|
|
435
|
+
await self._send_notification("notifications/initialized", {})
|
|
436
|
+
|
|
437
|
+
async def _send_request(self, method: str, params: dict = None) -> Any:
|
|
438
|
+
"""Send JSON-RPC request and wait for response."""
|
|
439
|
+
if not self._connected or not self._websocket:
|
|
440
|
+
raise MCPConnectionError("Not connected to WebSocket server")
|
|
441
|
+
|
|
442
|
+
request_id = self._get_next_id()
|
|
443
|
+
request = {
|
|
444
|
+
"jsonrpc": "2.0",
|
|
445
|
+
"id": request_id,
|
|
446
|
+
"method": method,
|
|
447
|
+
"params": params or {}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
# Create future for response
|
|
451
|
+
future = asyncio.Future()
|
|
452
|
+
self._response_futures[request_id] = future
|
|
453
|
+
|
|
454
|
+
try:
|
|
455
|
+
# Send request
|
|
456
|
+
await self._websocket.send_str(json.dumps(request))
|
|
457
|
+
|
|
458
|
+
# Wait for response (with timeout)
|
|
459
|
+
result = await asyncio.wait_for(future, timeout=30.0)
|
|
460
|
+
return result
|
|
461
|
+
|
|
462
|
+
except asyncio.TimeoutError:
|
|
463
|
+
self._response_futures.pop(request_id, None)
|
|
464
|
+
raise MCPConnectionError(f"Request timeout for method: {method}")
|
|
465
|
+
except Exception as e:
|
|
466
|
+
self._response_futures.pop(request_id, None)
|
|
467
|
+
raise
|
|
468
|
+
|
|
469
|
+
async def _send_notification(self, method: str, params: dict = None):
|
|
470
|
+
"""Send JSON-RPC notification (no response expected)."""
|
|
471
|
+
if not self._connected or not self._websocket:
|
|
472
|
+
raise MCPConnectionError("Not connected to WebSocket server")
|
|
473
|
+
|
|
474
|
+
notification = {
|
|
475
|
+
"jsonrpc": "2.0",
|
|
476
|
+
"method": method,
|
|
477
|
+
"params": params or {}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
await self._websocket.send_str(json.dumps(notification))
|
|
481
|
+
|
|
482
|
+
def _get_next_id(self) -> int:
|
|
483
|
+
"""Get next request ID."""
|
|
484
|
+
self._request_id += 1
|
|
485
|
+
return self._request_id
|
|
486
|
+
|
|
487
|
+
async def list_tools(self):
|
|
488
|
+
"""List available tools from MCP server."""
|
|
489
|
+
result = await self._send_request("tools/list", {})
|
|
490
|
+
|
|
491
|
+
# Convert to compatible format
|
|
492
|
+
tools = []
|
|
493
|
+
for tool_dict in result.get("tools", []):
|
|
494
|
+
# Create simple object with attributes
|
|
495
|
+
tool_obj = type('Tool', (), tool_dict)()
|
|
496
|
+
tools.append(tool_obj)
|
|
497
|
+
|
|
498
|
+
return tools
|
|
499
|
+
|
|
500
|
+
async def call_tool(self, tool_name: str, arguments: dict):
|
|
501
|
+
"""Call a tool on the MCP server."""
|
|
502
|
+
result = await self._send_request("tools/call", {
|
|
503
|
+
"name": tool_name,
|
|
504
|
+
"arguments": arguments
|
|
505
|
+
})
|
|
506
|
+
|
|
507
|
+
# Convert result to compatible format matching other transports
|
|
508
|
+
# MCP returns: {"content": [{"type": "text", "text": "..."}], "isError": false}
|
|
509
|
+
if isinstance(result, dict):
|
|
510
|
+
content_list = result.get("content", [])
|
|
511
|
+
# Convert to object with content attribute containing text items
|
|
512
|
+
result_obj = type('ToolResult', (), {
|
|
513
|
+
'content': [
|
|
514
|
+
type('ContentItem', (), item)()
|
|
515
|
+
for item in content_list
|
|
516
|
+
],
|
|
517
|
+
'isError': result.get("isError", False)
|
|
518
|
+
})()
|
|
519
|
+
return result_obj
|
|
520
|
+
|
|
521
|
+
return result
|
|
522
|
+
|
|
523
|
+
async def disconnect(self):
|
|
524
|
+
"""Disconnect from WebSocket server."""
|
|
525
|
+
self._connected = False
|
|
526
|
+
|
|
527
|
+
# Cancel receiver task
|
|
528
|
+
if self._receiver_task and not self._receiver_task.done():
|
|
529
|
+
self._receiver_task.cancel()
|
|
530
|
+
with contextlib.suppress(asyncio.CancelledError):
|
|
531
|
+
await self._receiver_task
|
|
532
|
+
|
|
533
|
+
# Close WebSocket
|
|
534
|
+
if self._websocket and not self._websocket.closed:
|
|
535
|
+
await self._websocket.close()
|
|
536
|
+
|
|
537
|
+
# Close session
|
|
538
|
+
if self._session and not self._session.closed:
|
|
539
|
+
await self._session.close()
|
|
540
|
+
|
|
541
|
+
# Clear futures
|
|
542
|
+
for future in self._response_futures.values():
|
|
543
|
+
if not future.done():
|
|
544
|
+
future.cancel()
|
|
545
|
+
self._response_futures.clear()
|
|
546
|
+
|
|
547
|
+
self.logger.info("Disconnected from WebSocket MCP server")
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from .abstract import ConversationMemory, ConversationHistory, ConversationTurn
|
|
2
|
+
from .mem import InMemoryConversation
|
|
3
|
+
from .redis import RedisConversation
|
|
4
|
+
from .file import FileConversationMemory
|
|
5
|
+
from .agent import AgentMemory
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"ConversationMemory",
|
|
10
|
+
"ConversationHistory",
|
|
11
|
+
"ConversationTurn",
|
|
12
|
+
"InMemoryConversation",
|
|
13
|
+
"FileConversationMemory",
|
|
14
|
+
"RedisConversation",
|
|
15
|
+
"AgentMemory",
|
|
16
|
+
]
|