PraisonAI 3.0.0__py3-none-any.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.
- praisonai/__init__.py +54 -0
- praisonai/__main__.py +15 -0
- praisonai/acp/__init__.py +54 -0
- praisonai/acp/config.py +159 -0
- praisonai/acp/server.py +587 -0
- praisonai/acp/session.py +219 -0
- praisonai/adapters/__init__.py +50 -0
- praisonai/adapters/readers.py +395 -0
- praisonai/adapters/rerankers.py +315 -0
- praisonai/adapters/retrievers.py +394 -0
- praisonai/adapters/vector_stores.py +409 -0
- praisonai/agent_scheduler.py +337 -0
- praisonai/agents_generator.py +903 -0
- praisonai/api/call.py +292 -0
- praisonai/auto.py +1197 -0
- praisonai/capabilities/__init__.py +275 -0
- praisonai/capabilities/a2a.py +140 -0
- praisonai/capabilities/assistants.py +283 -0
- praisonai/capabilities/audio.py +320 -0
- praisonai/capabilities/batches.py +469 -0
- praisonai/capabilities/completions.py +336 -0
- praisonai/capabilities/container_files.py +155 -0
- praisonai/capabilities/containers.py +93 -0
- praisonai/capabilities/embeddings.py +158 -0
- praisonai/capabilities/files.py +467 -0
- praisonai/capabilities/fine_tuning.py +293 -0
- praisonai/capabilities/guardrails.py +182 -0
- praisonai/capabilities/images.py +330 -0
- praisonai/capabilities/mcp.py +190 -0
- praisonai/capabilities/messages.py +270 -0
- praisonai/capabilities/moderations.py +154 -0
- praisonai/capabilities/ocr.py +217 -0
- praisonai/capabilities/passthrough.py +204 -0
- praisonai/capabilities/rag.py +207 -0
- praisonai/capabilities/realtime.py +160 -0
- praisonai/capabilities/rerank.py +165 -0
- praisonai/capabilities/responses.py +266 -0
- praisonai/capabilities/search.py +109 -0
- praisonai/capabilities/skills.py +133 -0
- praisonai/capabilities/vector_store_files.py +334 -0
- praisonai/capabilities/vector_stores.py +304 -0
- praisonai/capabilities/videos.py +141 -0
- praisonai/chainlit_ui.py +304 -0
- praisonai/chat/__init__.py +106 -0
- praisonai/chat/app.py +125 -0
- praisonai/cli/__init__.py +26 -0
- praisonai/cli/app.py +213 -0
- praisonai/cli/commands/__init__.py +75 -0
- praisonai/cli/commands/acp.py +70 -0
- praisonai/cli/commands/completion.py +333 -0
- praisonai/cli/commands/config.py +166 -0
- praisonai/cli/commands/debug.py +142 -0
- praisonai/cli/commands/diag.py +55 -0
- praisonai/cli/commands/doctor.py +166 -0
- praisonai/cli/commands/environment.py +179 -0
- praisonai/cli/commands/lsp.py +112 -0
- praisonai/cli/commands/mcp.py +210 -0
- praisonai/cli/commands/profile.py +457 -0
- praisonai/cli/commands/run.py +228 -0
- praisonai/cli/commands/schedule.py +150 -0
- praisonai/cli/commands/serve.py +97 -0
- praisonai/cli/commands/session.py +212 -0
- praisonai/cli/commands/traces.py +145 -0
- praisonai/cli/commands/version.py +101 -0
- praisonai/cli/configuration/__init__.py +18 -0
- praisonai/cli/configuration/loader.py +353 -0
- praisonai/cli/configuration/paths.py +114 -0
- praisonai/cli/configuration/schema.py +164 -0
- praisonai/cli/features/__init__.py +268 -0
- praisonai/cli/features/acp.py +236 -0
- praisonai/cli/features/action_orchestrator.py +546 -0
- praisonai/cli/features/agent_scheduler.py +773 -0
- praisonai/cli/features/agent_tools.py +474 -0
- praisonai/cli/features/agents.py +375 -0
- praisonai/cli/features/at_mentions.py +471 -0
- praisonai/cli/features/auto_memory.py +182 -0
- praisonai/cli/features/autonomy_mode.py +490 -0
- praisonai/cli/features/background.py +356 -0
- praisonai/cli/features/base.py +168 -0
- praisonai/cli/features/capabilities.py +1326 -0
- praisonai/cli/features/checkpoints.py +338 -0
- praisonai/cli/features/code_intelligence.py +652 -0
- praisonai/cli/features/compaction.py +294 -0
- praisonai/cli/features/compare.py +534 -0
- praisonai/cli/features/cost_tracker.py +514 -0
- praisonai/cli/features/debug.py +810 -0
- praisonai/cli/features/deploy.py +517 -0
- praisonai/cli/features/diag.py +289 -0
- praisonai/cli/features/doctor/__init__.py +63 -0
- praisonai/cli/features/doctor/checks/__init__.py +24 -0
- praisonai/cli/features/doctor/checks/acp_checks.py +240 -0
- praisonai/cli/features/doctor/checks/config_checks.py +366 -0
- praisonai/cli/features/doctor/checks/db_checks.py +366 -0
- praisonai/cli/features/doctor/checks/env_checks.py +543 -0
- praisonai/cli/features/doctor/checks/lsp_checks.py +199 -0
- praisonai/cli/features/doctor/checks/mcp_checks.py +349 -0
- praisonai/cli/features/doctor/checks/memory_checks.py +268 -0
- praisonai/cli/features/doctor/checks/network_checks.py +251 -0
- praisonai/cli/features/doctor/checks/obs_checks.py +328 -0
- praisonai/cli/features/doctor/checks/performance_checks.py +235 -0
- praisonai/cli/features/doctor/checks/permissions_checks.py +259 -0
- praisonai/cli/features/doctor/checks/selftest_checks.py +322 -0
- praisonai/cli/features/doctor/checks/serve_checks.py +426 -0
- praisonai/cli/features/doctor/checks/skills_checks.py +231 -0
- praisonai/cli/features/doctor/checks/tools_checks.py +371 -0
- praisonai/cli/features/doctor/engine.py +266 -0
- praisonai/cli/features/doctor/formatters.py +310 -0
- praisonai/cli/features/doctor/handler.py +397 -0
- praisonai/cli/features/doctor/models.py +264 -0
- praisonai/cli/features/doctor/registry.py +239 -0
- praisonai/cli/features/endpoints.py +1019 -0
- praisonai/cli/features/eval.py +560 -0
- praisonai/cli/features/external_agents.py +231 -0
- praisonai/cli/features/fast_context.py +410 -0
- praisonai/cli/features/flow_display.py +566 -0
- praisonai/cli/features/git_integration.py +651 -0
- praisonai/cli/features/guardrail.py +171 -0
- praisonai/cli/features/handoff.py +185 -0
- praisonai/cli/features/hooks.py +583 -0
- praisonai/cli/features/image.py +384 -0
- praisonai/cli/features/interactive_runtime.py +585 -0
- praisonai/cli/features/interactive_tools.py +380 -0
- praisonai/cli/features/interactive_tui.py +603 -0
- praisonai/cli/features/jobs.py +632 -0
- praisonai/cli/features/knowledge.py +531 -0
- praisonai/cli/features/lite.py +244 -0
- praisonai/cli/features/lsp_cli.py +225 -0
- praisonai/cli/features/mcp.py +169 -0
- praisonai/cli/features/message_queue.py +587 -0
- praisonai/cli/features/metrics.py +211 -0
- praisonai/cli/features/n8n.py +673 -0
- praisonai/cli/features/observability.py +293 -0
- praisonai/cli/features/ollama.py +361 -0
- praisonai/cli/features/output_style.py +273 -0
- praisonai/cli/features/package.py +631 -0
- praisonai/cli/features/performance.py +308 -0
- praisonai/cli/features/persistence.py +636 -0
- praisonai/cli/features/profile.py +226 -0
- praisonai/cli/features/profiler/__init__.py +81 -0
- praisonai/cli/features/profiler/core.py +558 -0
- praisonai/cli/features/profiler/optimizations.py +652 -0
- praisonai/cli/features/profiler/suite.py +386 -0
- praisonai/cli/features/profiling.py +350 -0
- praisonai/cli/features/queue/__init__.py +73 -0
- praisonai/cli/features/queue/manager.py +395 -0
- praisonai/cli/features/queue/models.py +286 -0
- praisonai/cli/features/queue/persistence.py +564 -0
- praisonai/cli/features/queue/scheduler.py +484 -0
- praisonai/cli/features/queue/worker.py +372 -0
- praisonai/cli/features/recipe.py +1723 -0
- praisonai/cli/features/recipes.py +449 -0
- praisonai/cli/features/registry.py +229 -0
- praisonai/cli/features/repo_map.py +860 -0
- praisonai/cli/features/router.py +466 -0
- praisonai/cli/features/sandbox_executor.py +515 -0
- praisonai/cli/features/serve.py +829 -0
- praisonai/cli/features/session.py +222 -0
- praisonai/cli/features/skills.py +856 -0
- praisonai/cli/features/slash_commands.py +650 -0
- praisonai/cli/features/telemetry.py +179 -0
- praisonai/cli/features/templates.py +1384 -0
- praisonai/cli/features/thinking.py +305 -0
- praisonai/cli/features/todo.py +334 -0
- praisonai/cli/features/tools.py +680 -0
- praisonai/cli/features/tui/__init__.py +83 -0
- praisonai/cli/features/tui/app.py +580 -0
- praisonai/cli/features/tui/cli.py +566 -0
- praisonai/cli/features/tui/debug.py +511 -0
- praisonai/cli/features/tui/events.py +99 -0
- praisonai/cli/features/tui/mock_provider.py +328 -0
- praisonai/cli/features/tui/orchestrator.py +652 -0
- praisonai/cli/features/tui/screens/__init__.py +50 -0
- praisonai/cli/features/tui/screens/main.py +245 -0
- praisonai/cli/features/tui/screens/queue.py +174 -0
- praisonai/cli/features/tui/screens/session.py +124 -0
- praisonai/cli/features/tui/screens/settings.py +148 -0
- praisonai/cli/features/tui/widgets/__init__.py +56 -0
- praisonai/cli/features/tui/widgets/chat.py +261 -0
- praisonai/cli/features/tui/widgets/composer.py +224 -0
- praisonai/cli/features/tui/widgets/queue_panel.py +200 -0
- praisonai/cli/features/tui/widgets/status.py +167 -0
- praisonai/cli/features/tui/widgets/tool_panel.py +248 -0
- praisonai/cli/features/workflow.py +720 -0
- praisonai/cli/legacy.py +236 -0
- praisonai/cli/main.py +5559 -0
- praisonai/cli/schedule_cli.py +54 -0
- praisonai/cli/state/__init__.py +31 -0
- praisonai/cli/state/identifiers.py +161 -0
- praisonai/cli/state/sessions.py +313 -0
- praisonai/code/__init__.py +93 -0
- praisonai/code/agent_tools.py +344 -0
- praisonai/code/diff/__init__.py +21 -0
- praisonai/code/diff/diff_strategy.py +432 -0
- praisonai/code/tools/__init__.py +27 -0
- praisonai/code/tools/apply_diff.py +221 -0
- praisonai/code/tools/execute_command.py +275 -0
- praisonai/code/tools/list_files.py +274 -0
- praisonai/code/tools/read_file.py +206 -0
- praisonai/code/tools/search_replace.py +248 -0
- praisonai/code/tools/write_file.py +217 -0
- praisonai/code/utils/__init__.py +46 -0
- praisonai/code/utils/file_utils.py +307 -0
- praisonai/code/utils/ignore_utils.py +308 -0
- praisonai/code/utils/text_utils.py +276 -0
- praisonai/db/__init__.py +64 -0
- praisonai/db/adapter.py +531 -0
- praisonai/deploy/__init__.py +62 -0
- praisonai/deploy/api.py +231 -0
- praisonai/deploy/docker.py +454 -0
- praisonai/deploy/doctor.py +367 -0
- praisonai/deploy/main.py +327 -0
- praisonai/deploy/models.py +179 -0
- praisonai/deploy/providers/__init__.py +33 -0
- praisonai/deploy/providers/aws.py +331 -0
- praisonai/deploy/providers/azure.py +358 -0
- praisonai/deploy/providers/base.py +101 -0
- praisonai/deploy/providers/gcp.py +314 -0
- praisonai/deploy/schema.py +208 -0
- praisonai/deploy.py +185 -0
- praisonai/endpoints/__init__.py +53 -0
- praisonai/endpoints/a2u_server.py +410 -0
- praisonai/endpoints/discovery.py +165 -0
- praisonai/endpoints/providers/__init__.py +28 -0
- praisonai/endpoints/providers/a2a.py +253 -0
- praisonai/endpoints/providers/a2u.py +208 -0
- praisonai/endpoints/providers/agents_api.py +171 -0
- praisonai/endpoints/providers/base.py +231 -0
- praisonai/endpoints/providers/mcp.py +263 -0
- praisonai/endpoints/providers/recipe.py +206 -0
- praisonai/endpoints/providers/tools_mcp.py +150 -0
- praisonai/endpoints/registry.py +131 -0
- praisonai/endpoints/server.py +161 -0
- praisonai/inbuilt_tools/__init__.py +24 -0
- praisonai/inbuilt_tools/autogen_tools.py +117 -0
- praisonai/inc/__init__.py +2 -0
- praisonai/inc/config.py +96 -0
- praisonai/inc/models.py +155 -0
- praisonai/integrations/__init__.py +56 -0
- praisonai/integrations/base.py +303 -0
- praisonai/integrations/claude_code.py +270 -0
- praisonai/integrations/codex_cli.py +255 -0
- praisonai/integrations/cursor_cli.py +195 -0
- praisonai/integrations/gemini_cli.py +222 -0
- praisonai/jobs/__init__.py +67 -0
- praisonai/jobs/executor.py +425 -0
- praisonai/jobs/models.py +230 -0
- praisonai/jobs/router.py +314 -0
- praisonai/jobs/server.py +186 -0
- praisonai/jobs/store.py +203 -0
- praisonai/llm/__init__.py +66 -0
- praisonai/llm/registry.py +382 -0
- praisonai/mcp_server/__init__.py +152 -0
- praisonai/mcp_server/adapters/__init__.py +74 -0
- praisonai/mcp_server/adapters/agents.py +128 -0
- praisonai/mcp_server/adapters/capabilities.py +168 -0
- praisonai/mcp_server/adapters/cli_tools.py +568 -0
- praisonai/mcp_server/adapters/extended_capabilities.py +462 -0
- praisonai/mcp_server/adapters/knowledge.py +93 -0
- praisonai/mcp_server/adapters/memory.py +104 -0
- praisonai/mcp_server/adapters/prompts.py +306 -0
- praisonai/mcp_server/adapters/resources.py +124 -0
- praisonai/mcp_server/adapters/tools_bridge.py +280 -0
- praisonai/mcp_server/auth/__init__.py +48 -0
- praisonai/mcp_server/auth/api_key.py +291 -0
- praisonai/mcp_server/auth/oauth.py +460 -0
- praisonai/mcp_server/auth/oidc.py +289 -0
- praisonai/mcp_server/auth/scopes.py +260 -0
- praisonai/mcp_server/cli.py +852 -0
- praisonai/mcp_server/elicitation.py +445 -0
- praisonai/mcp_server/icons.py +302 -0
- praisonai/mcp_server/recipe_adapter.py +573 -0
- praisonai/mcp_server/recipe_cli.py +824 -0
- praisonai/mcp_server/registry.py +703 -0
- praisonai/mcp_server/sampling.py +422 -0
- praisonai/mcp_server/server.py +490 -0
- praisonai/mcp_server/tasks.py +443 -0
- praisonai/mcp_server/transports/__init__.py +18 -0
- praisonai/mcp_server/transports/http_stream.py +376 -0
- praisonai/mcp_server/transports/stdio.py +132 -0
- praisonai/persistence/__init__.py +84 -0
- praisonai/persistence/config.py +238 -0
- praisonai/persistence/conversation/__init__.py +25 -0
- praisonai/persistence/conversation/async_mysql.py +427 -0
- praisonai/persistence/conversation/async_postgres.py +410 -0
- praisonai/persistence/conversation/async_sqlite.py +371 -0
- praisonai/persistence/conversation/base.py +151 -0
- praisonai/persistence/conversation/json_store.py +250 -0
- praisonai/persistence/conversation/mysql.py +387 -0
- praisonai/persistence/conversation/postgres.py +401 -0
- praisonai/persistence/conversation/singlestore.py +240 -0
- praisonai/persistence/conversation/sqlite.py +341 -0
- praisonai/persistence/conversation/supabase.py +203 -0
- praisonai/persistence/conversation/surrealdb.py +287 -0
- praisonai/persistence/factory.py +301 -0
- praisonai/persistence/hooks/__init__.py +18 -0
- praisonai/persistence/hooks/agent_hooks.py +297 -0
- praisonai/persistence/knowledge/__init__.py +26 -0
- praisonai/persistence/knowledge/base.py +144 -0
- praisonai/persistence/knowledge/cassandra.py +232 -0
- praisonai/persistence/knowledge/chroma.py +295 -0
- praisonai/persistence/knowledge/clickhouse.py +242 -0
- praisonai/persistence/knowledge/cosmosdb_vector.py +438 -0
- praisonai/persistence/knowledge/couchbase.py +286 -0
- praisonai/persistence/knowledge/lancedb.py +216 -0
- praisonai/persistence/knowledge/langchain_adapter.py +291 -0
- praisonai/persistence/knowledge/lightrag_adapter.py +212 -0
- praisonai/persistence/knowledge/llamaindex_adapter.py +256 -0
- praisonai/persistence/knowledge/milvus.py +277 -0
- praisonai/persistence/knowledge/mongodb_vector.py +306 -0
- praisonai/persistence/knowledge/pgvector.py +335 -0
- praisonai/persistence/knowledge/pinecone.py +253 -0
- praisonai/persistence/knowledge/qdrant.py +301 -0
- praisonai/persistence/knowledge/redis_vector.py +291 -0
- praisonai/persistence/knowledge/singlestore_vector.py +299 -0
- praisonai/persistence/knowledge/surrealdb_vector.py +309 -0
- praisonai/persistence/knowledge/upstash_vector.py +266 -0
- praisonai/persistence/knowledge/weaviate.py +223 -0
- praisonai/persistence/migrations/__init__.py +10 -0
- praisonai/persistence/migrations/manager.py +251 -0
- praisonai/persistence/orchestrator.py +406 -0
- praisonai/persistence/state/__init__.py +21 -0
- praisonai/persistence/state/async_mongodb.py +200 -0
- praisonai/persistence/state/base.py +107 -0
- praisonai/persistence/state/dynamodb.py +226 -0
- praisonai/persistence/state/firestore.py +175 -0
- praisonai/persistence/state/gcs.py +155 -0
- praisonai/persistence/state/memory.py +245 -0
- praisonai/persistence/state/mongodb.py +158 -0
- praisonai/persistence/state/redis.py +190 -0
- praisonai/persistence/state/upstash.py +144 -0
- praisonai/persistence/tests/__init__.py +3 -0
- praisonai/persistence/tests/test_all_backends.py +633 -0
- praisonai/profiler.py +1214 -0
- praisonai/recipe/__init__.py +134 -0
- praisonai/recipe/bridge.py +278 -0
- praisonai/recipe/core.py +893 -0
- praisonai/recipe/exceptions.py +54 -0
- praisonai/recipe/history.py +402 -0
- praisonai/recipe/models.py +266 -0
- praisonai/recipe/operations.py +440 -0
- praisonai/recipe/policy.py +422 -0
- praisonai/recipe/registry.py +849 -0
- praisonai/recipe/runtime.py +214 -0
- praisonai/recipe/security.py +711 -0
- praisonai/recipe/serve.py +859 -0
- praisonai/recipe/server.py +613 -0
- praisonai/scheduler/__init__.py +45 -0
- praisonai/scheduler/agent_scheduler.py +552 -0
- praisonai/scheduler/base.py +124 -0
- praisonai/scheduler/daemon_manager.py +225 -0
- praisonai/scheduler/state_manager.py +155 -0
- praisonai/scheduler/yaml_loader.py +193 -0
- praisonai/scheduler.py +194 -0
- praisonai/setup/__init__.py +1 -0
- praisonai/setup/build.py +21 -0
- praisonai/setup/post_install.py +23 -0
- praisonai/setup/setup_conda_env.py +25 -0
- praisonai/setup.py +16 -0
- praisonai/templates/__init__.py +116 -0
- praisonai/templates/cache.py +364 -0
- praisonai/templates/dependency_checker.py +358 -0
- praisonai/templates/discovery.py +391 -0
- praisonai/templates/loader.py +564 -0
- praisonai/templates/registry.py +511 -0
- praisonai/templates/resolver.py +206 -0
- praisonai/templates/security.py +327 -0
- praisonai/templates/tool_override.py +498 -0
- praisonai/templates/tools_doctor.py +256 -0
- praisonai/test.py +105 -0
- praisonai/train.py +562 -0
- praisonai/train_vision.py +306 -0
- praisonai/ui/agents.py +824 -0
- praisonai/ui/callbacks.py +57 -0
- praisonai/ui/chainlit_compat.py +246 -0
- praisonai/ui/chat.py +532 -0
- praisonai/ui/code.py +717 -0
- praisonai/ui/colab.py +474 -0
- praisonai/ui/colab_chainlit.py +81 -0
- praisonai/ui/components/aicoder.py +284 -0
- praisonai/ui/context.py +283 -0
- praisonai/ui/database_config.py +56 -0
- praisonai/ui/db.py +294 -0
- praisonai/ui/realtime.py +488 -0
- praisonai/ui/realtimeclient/__init__.py +756 -0
- praisonai/ui/realtimeclient/tools.py +242 -0
- praisonai/ui/sql_alchemy.py +710 -0
- praisonai/upload_vision.py +140 -0
- praisonai/version.py +1 -0
- praisonai-3.0.0.dist-info/METADATA +3493 -0
- praisonai-3.0.0.dist-info/RECORD +393 -0
- praisonai-3.0.0.dist-info/WHEEL +5 -0
- praisonai-3.0.0.dist-info/entry_points.txt +4 -0
- praisonai-3.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP Tasks API Implementation
|
|
3
|
+
|
|
4
|
+
Implements the Tasks API per MCP 2025-11-25 specification.
|
|
5
|
+
Tasks are durable state machines for tracking long-running operations.
|
|
6
|
+
|
|
7
|
+
Features:
|
|
8
|
+
- Task creation, update, cancellation
|
|
9
|
+
- Polling and deferred result retrieval
|
|
10
|
+
- In-memory storage (default) with optional DB adapters
|
|
11
|
+
- Session-scoped task management
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import asyncio
|
|
15
|
+
import logging
|
|
16
|
+
import time
|
|
17
|
+
import uuid
|
|
18
|
+
from dataclasses import dataclass, field
|
|
19
|
+
from datetime import datetime, timezone
|
|
20
|
+
from enum import Enum
|
|
21
|
+
from typing import Any, Callable, Dict, List, Optional
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _iso_now() -> str:
|
|
27
|
+
"""Get current time as ISO 8601 string."""
|
|
28
|
+
return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class TaskStatus(str, Enum):
|
|
32
|
+
"""Task status values per MCP 2025-11-25 specification."""
|
|
33
|
+
PENDING = "pending" # Task created but not yet started
|
|
34
|
+
WORKING = "working" # Task is actively being processed
|
|
35
|
+
INPUT_REQUIRED = "input_required" # Task needs user input (elicitation)
|
|
36
|
+
COMPLETED = "completed" # Task finished successfully
|
|
37
|
+
FAILED = "failed" # Task failed with error
|
|
38
|
+
CANCELLED = "cancelled" # Task was cancelled
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# Alias for backwards compatibility
|
|
42
|
+
TaskState = TaskStatus
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass
|
|
46
|
+
class TaskProgress:
|
|
47
|
+
"""Task progress information."""
|
|
48
|
+
current: float = 0.0
|
|
49
|
+
total: Optional[float] = None
|
|
50
|
+
message: Optional[str] = None
|
|
51
|
+
|
|
52
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
53
|
+
result = {"current": self.current}
|
|
54
|
+
if self.total is not None:
|
|
55
|
+
result["total"] = self.total
|
|
56
|
+
if self.message:
|
|
57
|
+
result["message"] = self.message
|
|
58
|
+
return result
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@dataclass
|
|
62
|
+
class Task:
|
|
63
|
+
"""
|
|
64
|
+
MCP Task representation per 2025-11-25 specification.
|
|
65
|
+
|
|
66
|
+
Tasks are durable state machines that carry information about the underlying
|
|
67
|
+
execution state of requests, intended for requestor polling and deferred result retrieval.
|
|
68
|
+
"""
|
|
69
|
+
id: str # Internal ID (maps to taskId in protocol)
|
|
70
|
+
method: str
|
|
71
|
+
params: Dict[str, Any] = field(default_factory=dict)
|
|
72
|
+
status: TaskStatus = TaskStatus.PENDING
|
|
73
|
+
status_message: Optional[str] = None
|
|
74
|
+
progress: Optional[TaskProgress] = None
|
|
75
|
+
result: Any = None
|
|
76
|
+
error: Optional[Dict[str, Any]] = None
|
|
77
|
+
created_at: str = field(default_factory=lambda: _iso_now())
|
|
78
|
+
last_updated_at: str = field(default_factory=lambda: _iso_now())
|
|
79
|
+
ttl: Optional[int] = None # TTL in milliseconds
|
|
80
|
+
poll_interval: int = 5000 # Recommended poll interval in milliseconds
|
|
81
|
+
session_id: Optional[str] = None
|
|
82
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
83
|
+
|
|
84
|
+
# Backwards compatibility alias
|
|
85
|
+
@property
|
|
86
|
+
def state(self) -> TaskStatus:
|
|
87
|
+
return self.status
|
|
88
|
+
|
|
89
|
+
@state.setter
|
|
90
|
+
def state(self, value: TaskStatus) -> None:
|
|
91
|
+
self.status = value
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def updated_at(self) -> str:
|
|
95
|
+
return self.last_updated_at
|
|
96
|
+
|
|
97
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
98
|
+
"""Convert to MCP task response format per 2025-11-25 spec."""
|
|
99
|
+
result = {
|
|
100
|
+
"taskId": self.id,
|
|
101
|
+
"status": self.status.value,
|
|
102
|
+
"createdAt": self.created_at,
|
|
103
|
+
"lastUpdatedAt": self.last_updated_at,
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if self.status_message:
|
|
107
|
+
result["statusMessage"] = self.status_message
|
|
108
|
+
|
|
109
|
+
if self.ttl is not None:
|
|
110
|
+
result["ttl"] = self.ttl
|
|
111
|
+
|
|
112
|
+
if self.poll_interval:
|
|
113
|
+
result["pollInterval"] = self.poll_interval
|
|
114
|
+
|
|
115
|
+
if self.progress:
|
|
116
|
+
result["progress"] = self.progress.to_dict()
|
|
117
|
+
|
|
118
|
+
if self.metadata:
|
|
119
|
+
result["_meta"] = self.metadata
|
|
120
|
+
|
|
121
|
+
return result
|
|
122
|
+
|
|
123
|
+
def to_create_result(self) -> Dict[str, Any]:
|
|
124
|
+
"""Convert to CreateTaskResult format."""
|
|
125
|
+
return {"task": self.to_dict()}
|
|
126
|
+
|
|
127
|
+
def to_get_result(self) -> Dict[str, Any]:
|
|
128
|
+
"""Convert to GetTaskResult format."""
|
|
129
|
+
return self.to_dict()
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class TaskStore:
|
|
133
|
+
"""
|
|
134
|
+
In-memory task storage.
|
|
135
|
+
|
|
136
|
+
Can be extended with DB adapters for persistence.
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
def __init__(self, max_tasks: int = 1000, ttl: int = 3600):
|
|
140
|
+
"""
|
|
141
|
+
Initialize task store.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
max_tasks: Maximum number of tasks to store
|
|
145
|
+
ttl: Task TTL in seconds (for cleanup)
|
|
146
|
+
"""
|
|
147
|
+
self._tasks: Dict[str, Task] = {}
|
|
148
|
+
self._max_tasks = max_tasks
|
|
149
|
+
self._ttl = ttl
|
|
150
|
+
|
|
151
|
+
def create(
|
|
152
|
+
self,
|
|
153
|
+
method: str,
|
|
154
|
+
params: Dict[str, Any],
|
|
155
|
+
session_id: Optional[str] = None,
|
|
156
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
157
|
+
) -> Task:
|
|
158
|
+
"""Create a new task."""
|
|
159
|
+
# Cleanup old tasks if at capacity
|
|
160
|
+
if len(self._tasks) >= self._max_tasks:
|
|
161
|
+
self._cleanup_old_tasks()
|
|
162
|
+
|
|
163
|
+
task_id = f"task-{uuid.uuid4().hex[:16]}"
|
|
164
|
+
task = Task(
|
|
165
|
+
id=task_id,
|
|
166
|
+
method=method,
|
|
167
|
+
params=params,
|
|
168
|
+
session_id=session_id,
|
|
169
|
+
metadata=metadata or {},
|
|
170
|
+
)
|
|
171
|
+
self._tasks[task_id] = task
|
|
172
|
+
logger.debug(f"Created task: {task_id}")
|
|
173
|
+
return task
|
|
174
|
+
|
|
175
|
+
def get(self, task_id: str) -> Optional[Task]:
|
|
176
|
+
"""Get a task by ID."""
|
|
177
|
+
return self._tasks.get(task_id)
|
|
178
|
+
|
|
179
|
+
def update(
|
|
180
|
+
self,
|
|
181
|
+
task_id: str,
|
|
182
|
+
status: Optional[TaskStatus] = None,
|
|
183
|
+
status_message: Optional[str] = None,
|
|
184
|
+
progress: Optional[TaskProgress] = None,
|
|
185
|
+
result: Any = None,
|
|
186
|
+
error: Optional[Dict[str, Any]] = None,
|
|
187
|
+
state: Optional[TaskStatus] = None, # Backwards compat alias
|
|
188
|
+
) -> Optional[Task]:
|
|
189
|
+
"""Update a task."""
|
|
190
|
+
task = self._tasks.get(task_id)
|
|
191
|
+
if not task:
|
|
192
|
+
return None
|
|
193
|
+
|
|
194
|
+
task.last_updated_at = _iso_now()
|
|
195
|
+
|
|
196
|
+
# Support both status and state (backwards compat)
|
|
197
|
+
new_status = status or state
|
|
198
|
+
if new_status:
|
|
199
|
+
task.status = new_status
|
|
200
|
+
|
|
201
|
+
if status_message:
|
|
202
|
+
task.status_message = status_message
|
|
203
|
+
|
|
204
|
+
if progress:
|
|
205
|
+
task.progress = progress
|
|
206
|
+
|
|
207
|
+
if result is not None:
|
|
208
|
+
task.result = result
|
|
209
|
+
|
|
210
|
+
if error:
|
|
211
|
+
task.error = error
|
|
212
|
+
|
|
213
|
+
logger.debug(f"Updated task {task_id}: status={task.status}")
|
|
214
|
+
return task
|
|
215
|
+
|
|
216
|
+
def cancel(self, task_id: str) -> Optional[Task]:
|
|
217
|
+
"""Cancel a task."""
|
|
218
|
+
task = self._tasks.get(task_id)
|
|
219
|
+
if not task:
|
|
220
|
+
return None
|
|
221
|
+
|
|
222
|
+
if task.status in (TaskStatus.PENDING, TaskStatus.WORKING):
|
|
223
|
+
task.status = TaskStatus.CANCELLED
|
|
224
|
+
task.status_message = "The task was cancelled by request."
|
|
225
|
+
task.last_updated_at = _iso_now()
|
|
226
|
+
logger.debug(f"Cancelled task: {task_id}")
|
|
227
|
+
|
|
228
|
+
return task
|
|
229
|
+
|
|
230
|
+
def delete(self, task_id: str) -> bool:
|
|
231
|
+
"""Delete a task."""
|
|
232
|
+
if task_id in self._tasks:
|
|
233
|
+
del self._tasks[task_id]
|
|
234
|
+
return True
|
|
235
|
+
return False
|
|
236
|
+
|
|
237
|
+
def list_tasks(
|
|
238
|
+
self,
|
|
239
|
+
session_id: Optional[str] = None,
|
|
240
|
+
status: Optional[TaskStatus] = None,
|
|
241
|
+
state: Optional[TaskStatus] = None, # Backwards compat alias
|
|
242
|
+
limit: int = 100,
|
|
243
|
+
) -> List[Task]:
|
|
244
|
+
"""List tasks with optional filtering."""
|
|
245
|
+
tasks = list(self._tasks.values())
|
|
246
|
+
filter_status = status or state
|
|
247
|
+
|
|
248
|
+
if session_id:
|
|
249
|
+
tasks = [t for t in tasks if t.session_id == session_id]
|
|
250
|
+
|
|
251
|
+
if filter_status:
|
|
252
|
+
tasks = [t for t in tasks if t.status == filter_status]
|
|
253
|
+
|
|
254
|
+
# Sort by created_at descending
|
|
255
|
+
tasks.sort(key=lambda t: t.created_at, reverse=True)
|
|
256
|
+
|
|
257
|
+
return tasks[:limit]
|
|
258
|
+
|
|
259
|
+
def _cleanup_old_tasks(self) -> None:
|
|
260
|
+
"""Remove old completed/failed tasks."""
|
|
261
|
+
now = time.time()
|
|
262
|
+
to_remove = []
|
|
263
|
+
|
|
264
|
+
for task_id, task in self._tasks.items():
|
|
265
|
+
if task.status in (TaskStatus.COMPLETED, TaskStatus.FAILED, TaskStatus.CANCELLED):
|
|
266
|
+
# Parse ISO timestamp to compare
|
|
267
|
+
try:
|
|
268
|
+
from datetime import datetime
|
|
269
|
+
updated = datetime.fromisoformat(task.last_updated_at.replace("Z", "+00:00"))
|
|
270
|
+
age = now - updated.timestamp()
|
|
271
|
+
if age > self._ttl:
|
|
272
|
+
to_remove.append(task_id)
|
|
273
|
+
except (ValueError, AttributeError):
|
|
274
|
+
pass
|
|
275
|
+
|
|
276
|
+
for task_id in to_remove:
|
|
277
|
+
del self._tasks[task_id]
|
|
278
|
+
|
|
279
|
+
logger.debug(f"Cleaned up {len(to_remove)} old tasks")
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
class TaskManager:
|
|
283
|
+
"""
|
|
284
|
+
MCP Task Manager.
|
|
285
|
+
|
|
286
|
+
Handles task lifecycle and execution.
|
|
287
|
+
"""
|
|
288
|
+
|
|
289
|
+
def __init__(
|
|
290
|
+
self,
|
|
291
|
+
store: Optional[TaskStore] = None,
|
|
292
|
+
executor: Optional[Callable] = None,
|
|
293
|
+
):
|
|
294
|
+
"""
|
|
295
|
+
Initialize task manager.
|
|
296
|
+
|
|
297
|
+
Args:
|
|
298
|
+
store: Task storage (uses in-memory if None)
|
|
299
|
+
executor: Optional async executor for task execution
|
|
300
|
+
"""
|
|
301
|
+
self._store = store or TaskStore()
|
|
302
|
+
self._executor = executor
|
|
303
|
+
self._running_tasks: Dict[str, asyncio.Task] = {}
|
|
304
|
+
|
|
305
|
+
async def create_task(
|
|
306
|
+
self,
|
|
307
|
+
method: str,
|
|
308
|
+
params: Dict[str, Any],
|
|
309
|
+
session_id: Optional[str] = None,
|
|
310
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
311
|
+
execute: bool = True,
|
|
312
|
+
) -> Task:
|
|
313
|
+
"""
|
|
314
|
+
Create and optionally start executing a task.
|
|
315
|
+
|
|
316
|
+
Args:
|
|
317
|
+
method: Method name for the task
|
|
318
|
+
params: Task parameters
|
|
319
|
+
session_id: Optional session ID
|
|
320
|
+
metadata: Optional metadata
|
|
321
|
+
execute: Whether to start execution immediately
|
|
322
|
+
|
|
323
|
+
Returns:
|
|
324
|
+
Created task
|
|
325
|
+
"""
|
|
326
|
+
task = self._store.create(method, params, session_id, metadata)
|
|
327
|
+
|
|
328
|
+
if execute and self._executor:
|
|
329
|
+
# Start async execution
|
|
330
|
+
asyncio_task = asyncio.create_task(
|
|
331
|
+
self._execute_task(task.id)
|
|
332
|
+
)
|
|
333
|
+
self._running_tasks[task.id] = asyncio_task
|
|
334
|
+
|
|
335
|
+
return task
|
|
336
|
+
|
|
337
|
+
async def _execute_task(self, task_id: str) -> None:
|
|
338
|
+
"""Execute a task asynchronously."""
|
|
339
|
+
task = self._store.get(task_id)
|
|
340
|
+
if not task:
|
|
341
|
+
return
|
|
342
|
+
|
|
343
|
+
try:
|
|
344
|
+
# Update to working
|
|
345
|
+
self._store.update(
|
|
346
|
+
task_id,
|
|
347
|
+
status=TaskStatus.WORKING,
|
|
348
|
+
status_message="The operation is now in progress.",
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
# Execute
|
|
352
|
+
if self._executor:
|
|
353
|
+
result = await self._executor(task.method, task.params)
|
|
354
|
+
self._store.update(
|
|
355
|
+
task_id,
|
|
356
|
+
status=TaskStatus.COMPLETED,
|
|
357
|
+
status_message="The operation completed successfully.",
|
|
358
|
+
result=result,
|
|
359
|
+
)
|
|
360
|
+
else:
|
|
361
|
+
self._store.update(
|
|
362
|
+
task_id,
|
|
363
|
+
status=TaskStatus.FAILED,
|
|
364
|
+
error={"code": -32603, "message": "No executor configured"},
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
except asyncio.CancelledError:
|
|
368
|
+
self._store.update(
|
|
369
|
+
task_id,
|
|
370
|
+
status=TaskStatus.CANCELLED,
|
|
371
|
+
status_message="The task was cancelled.",
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
except Exception as e:
|
|
375
|
+
logger.exception(f"Task execution failed: {task_id}")
|
|
376
|
+
self._store.update(
|
|
377
|
+
task_id,
|
|
378
|
+
status=TaskStatus.FAILED,
|
|
379
|
+
status_message=f"Task failed: {str(e)}",
|
|
380
|
+
error={"code": -32603, "message": str(e)},
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
finally:
|
|
384
|
+
if task_id in self._running_tasks:
|
|
385
|
+
del self._running_tasks[task_id]
|
|
386
|
+
|
|
387
|
+
def get_task(self, task_id: str) -> Optional[Task]:
|
|
388
|
+
"""Get a task by ID."""
|
|
389
|
+
return self._store.get(task_id)
|
|
390
|
+
|
|
391
|
+
def update_progress(
|
|
392
|
+
self,
|
|
393
|
+
task_id: str,
|
|
394
|
+
current: float,
|
|
395
|
+
total: Optional[float] = None,
|
|
396
|
+
message: Optional[str] = None,
|
|
397
|
+
) -> Optional[Task]:
|
|
398
|
+
"""Update task progress."""
|
|
399
|
+
progress = TaskProgress(current=current, total=total, message=message)
|
|
400
|
+
return self._store.update(task_id, progress=progress)
|
|
401
|
+
|
|
402
|
+
async def cancel_task(self, task_id: str) -> Optional[Task]:
|
|
403
|
+
"""Cancel a task."""
|
|
404
|
+
# Cancel running asyncio task if exists
|
|
405
|
+
if task_id in self._running_tasks:
|
|
406
|
+
self._running_tasks[task_id].cancel()
|
|
407
|
+
try:
|
|
408
|
+
await self._running_tasks[task_id]
|
|
409
|
+
except asyncio.CancelledError:
|
|
410
|
+
pass
|
|
411
|
+
|
|
412
|
+
return self._store.cancel(task_id)
|
|
413
|
+
|
|
414
|
+
def list_tasks(
|
|
415
|
+
self,
|
|
416
|
+
session_id: Optional[str] = None,
|
|
417
|
+
state: Optional[TaskState] = None,
|
|
418
|
+
limit: int = 100,
|
|
419
|
+
) -> List[Task]:
|
|
420
|
+
"""List tasks."""
|
|
421
|
+
return self._store.list_tasks(session_id, state, limit)
|
|
422
|
+
|
|
423
|
+
def delete_task(self, task_id: str) -> bool:
|
|
424
|
+
"""Delete a task."""
|
|
425
|
+
return self._store.delete(task_id)
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
# Global task manager instance
|
|
429
|
+
_task_manager: Optional[TaskManager] = None
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
def get_task_manager() -> TaskManager:
|
|
433
|
+
"""Get the global task manager."""
|
|
434
|
+
global _task_manager
|
|
435
|
+
if _task_manager is None:
|
|
436
|
+
_task_manager = TaskManager()
|
|
437
|
+
return _task_manager
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
def set_task_manager(manager: TaskManager) -> None:
|
|
441
|
+
"""Set the global task manager."""
|
|
442
|
+
global _task_manager
|
|
443
|
+
_task_manager = manager
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP Transport Implementations
|
|
3
|
+
|
|
4
|
+
Provides STDIO and HTTP Stream transports for the MCP server.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
__all__ = ["StdioTransport", "HTTPStreamTransport"]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def __getattr__(name):
|
|
11
|
+
"""Lazy load transports."""
|
|
12
|
+
if name == "StdioTransport":
|
|
13
|
+
from .stdio import StdioTransport
|
|
14
|
+
return StdioTransport
|
|
15
|
+
elif name == "HTTPStreamTransport":
|
|
16
|
+
from .http_stream import HTTPStreamTransport
|
|
17
|
+
return HTTPStreamTransport
|
|
18
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|