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,824 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Recipe MCP Server CLI Commands
|
|
3
|
+
|
|
4
|
+
CLI commands for serving recipes as MCP servers.
|
|
5
|
+
|
|
6
|
+
Commands:
|
|
7
|
+
- serve-recipe: Serve a recipe as MCP server
|
|
8
|
+
- list-recipes: List available recipes for MCP serving
|
|
9
|
+
- validate-recipe: Validate recipe MCP compatibility
|
|
10
|
+
- inspect-recipe: Inspect recipe MCP schema
|
|
11
|
+
- config-generate-recipe: Generate client config for recipe server
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import argparse
|
|
15
|
+
import json
|
|
16
|
+
import logging
|
|
17
|
+
import sys
|
|
18
|
+
from typing import List
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class RecipeMCPCLI:
|
|
24
|
+
"""CLI handler for recipe MCP server commands."""
|
|
25
|
+
|
|
26
|
+
EXIT_SUCCESS = 0
|
|
27
|
+
EXIT_ERROR = 1
|
|
28
|
+
|
|
29
|
+
def __init__(self):
|
|
30
|
+
"""Initialize CLI handler."""
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
def handle(self, args: List[str]) -> int:
|
|
34
|
+
"""
|
|
35
|
+
Handle recipe MCP CLI subcommand.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
args: Command arguments
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
Exit code
|
|
42
|
+
"""
|
|
43
|
+
if not args:
|
|
44
|
+
self._print_help()
|
|
45
|
+
return self.EXIT_SUCCESS
|
|
46
|
+
|
|
47
|
+
command = args[0]
|
|
48
|
+
remaining = args[1:]
|
|
49
|
+
|
|
50
|
+
commands = {
|
|
51
|
+
"serve-recipe": self.cmd_serve_recipe,
|
|
52
|
+
"list-recipes": self.cmd_list_recipes,
|
|
53
|
+
"validate-recipe": self.cmd_validate_recipe,
|
|
54
|
+
"inspect-recipe": self.cmd_inspect_recipe,
|
|
55
|
+
"config-generate-recipe": self.cmd_config_generate_recipe,
|
|
56
|
+
"auth": self.cmd_auth,
|
|
57
|
+
"tasks": self.cmd_tasks,
|
|
58
|
+
"help": lambda _: self._print_help() or self.EXIT_SUCCESS,
|
|
59
|
+
"--help": lambda _: self._print_help() or self.EXIT_SUCCESS,
|
|
60
|
+
"-h": lambda _: self._print_help() or self.EXIT_SUCCESS,
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if command in commands:
|
|
64
|
+
return commands[command](remaining)
|
|
65
|
+
else:
|
|
66
|
+
self._print_error(f"Unknown command: {command}")
|
|
67
|
+
self._print_help()
|
|
68
|
+
return self.EXIT_ERROR
|
|
69
|
+
|
|
70
|
+
def _print_help(self) -> None:
|
|
71
|
+
"""Print help message."""
|
|
72
|
+
help_text = """
|
|
73
|
+
[bold cyan]PraisonAI Recipe MCP Server (Protocol Version 2025-11-25)[/bold cyan]
|
|
74
|
+
|
|
75
|
+
Serve PraisonAI recipes as MCP servers for Claude Desktop, Cursor, Windsurf, and other MCP clients.
|
|
76
|
+
|
|
77
|
+
[bold]Usage:[/bold]
|
|
78
|
+
praisonai mcp <command> [options]
|
|
79
|
+
|
|
80
|
+
[bold]Recipe Commands:[/bold]
|
|
81
|
+
serve-recipe <name> Serve a recipe as MCP server
|
|
82
|
+
list-recipes List available recipes
|
|
83
|
+
validate-recipe <name> Validate recipe MCP compatibility
|
|
84
|
+
inspect-recipe <name> Inspect recipe MCP schema
|
|
85
|
+
config-generate-recipe Generate client config for recipe
|
|
86
|
+
|
|
87
|
+
[bold]Auth Commands:[/bold]
|
|
88
|
+
auth generate-key Generate API key
|
|
89
|
+
auth validate <key> Validate API key
|
|
90
|
+
auth oidc-discover <url> Discover OIDC configuration
|
|
91
|
+
|
|
92
|
+
[bold]Tasks Commands:[/bold]
|
|
93
|
+
tasks list List tasks
|
|
94
|
+
tasks get <id> Get task details
|
|
95
|
+
tasks cancel <id> Cancel a task
|
|
96
|
+
|
|
97
|
+
[bold]Serve Recipe Options:[/bold]
|
|
98
|
+
--transport <type> Transport: stdio (default) or http-stream
|
|
99
|
+
--host <host> HTTP host (default: 127.0.0.1)
|
|
100
|
+
--port <port> HTTP port (default: 8080)
|
|
101
|
+
--endpoint <path> HTTP endpoint (default: /mcp)
|
|
102
|
+
--api-key <key> API key for authentication
|
|
103
|
+
--safe-mode/--no-safe-mode Enable/disable safe mode (default: enabled)
|
|
104
|
+
--expose-tools/--no-expose-tools Expose agent tools (default: yes)
|
|
105
|
+
--expose-prompts/--no-expose-prompts Expose prompts (default: yes)
|
|
106
|
+
--session-ttl <secs> Session TTL in seconds (default: 3600)
|
|
107
|
+
--log-level <level> Log level: debug, info, warning, error
|
|
108
|
+
--json Output in JSON format
|
|
109
|
+
|
|
110
|
+
[bold]Examples:[/bold]
|
|
111
|
+
# Serve a recipe as STDIO MCP server
|
|
112
|
+
praisonai mcp serve-recipe support-reply --transport stdio
|
|
113
|
+
|
|
114
|
+
# Serve with HTTP Stream transport
|
|
115
|
+
praisonai mcp serve-recipe ai-video-editor --transport http-stream --port 8080
|
|
116
|
+
|
|
117
|
+
# List available recipes
|
|
118
|
+
praisonai mcp list-recipes
|
|
119
|
+
|
|
120
|
+
# Validate recipe MCP compatibility
|
|
121
|
+
praisonai mcp validate-recipe support-reply
|
|
122
|
+
|
|
123
|
+
# Inspect recipe tools/resources/prompts
|
|
124
|
+
praisonai mcp inspect-recipe support-reply --tools
|
|
125
|
+
|
|
126
|
+
# Generate Claude Desktop config for recipe
|
|
127
|
+
praisonai mcp config-generate-recipe support-reply --client claude-desktop
|
|
128
|
+
"""
|
|
129
|
+
self._print_rich(help_text)
|
|
130
|
+
|
|
131
|
+
def _print_rich(self, text: str) -> None:
|
|
132
|
+
"""Print with rich formatting if available."""
|
|
133
|
+
try:
|
|
134
|
+
from rich import print as rprint
|
|
135
|
+
rprint(text)
|
|
136
|
+
except ImportError:
|
|
137
|
+
import re
|
|
138
|
+
plain = re.sub(r'\[/?[^\]]+\]', '', text)
|
|
139
|
+
print(plain)
|
|
140
|
+
|
|
141
|
+
def _print_error(self, message: str) -> None:
|
|
142
|
+
"""Print error message."""
|
|
143
|
+
try:
|
|
144
|
+
from rich import print as rprint
|
|
145
|
+
rprint(f"[red]Error: {message}[/red]")
|
|
146
|
+
except ImportError:
|
|
147
|
+
print(f"Error: {message}", file=sys.stderr)
|
|
148
|
+
|
|
149
|
+
def _print_success(self, message: str) -> None:
|
|
150
|
+
"""Print success message."""
|
|
151
|
+
try:
|
|
152
|
+
from rich import print as rprint
|
|
153
|
+
rprint(f"[green]✓ {message}[/green]")
|
|
154
|
+
except ImportError:
|
|
155
|
+
print(f"✓ {message}")
|
|
156
|
+
|
|
157
|
+
def _print_json(self, data: dict) -> None:
|
|
158
|
+
"""Print JSON output."""
|
|
159
|
+
print(json.dumps(data, indent=2))
|
|
160
|
+
|
|
161
|
+
def cmd_serve_recipe(self, args: List[str]) -> int:
|
|
162
|
+
"""Serve a recipe as MCP server."""
|
|
163
|
+
parser = argparse.ArgumentParser(prog="praisonai mcp serve-recipe")
|
|
164
|
+
parser.add_argument("recipe_name", help="Recipe name to serve")
|
|
165
|
+
parser.add_argument("--transport", default="stdio", choices=["stdio", "http-stream"])
|
|
166
|
+
parser.add_argument("--host", default="127.0.0.1")
|
|
167
|
+
parser.add_argument("--port", type=int, default=8080)
|
|
168
|
+
parser.add_argument("--endpoint", default="/mcp")
|
|
169
|
+
parser.add_argument("--api-key", default=None)
|
|
170
|
+
parser.add_argument("--safe-mode", action="store_true", default=True)
|
|
171
|
+
parser.add_argument("--no-safe-mode", action="store_true")
|
|
172
|
+
parser.add_argument("--expose-tools", action="store_true", default=True)
|
|
173
|
+
parser.add_argument("--no-expose-tools", action="store_true")
|
|
174
|
+
parser.add_argument("--expose-prompts", action="store_true", default=True)
|
|
175
|
+
parser.add_argument("--no-expose-prompts", action="store_true")
|
|
176
|
+
parser.add_argument("--session-ttl", type=int, default=3600)
|
|
177
|
+
parser.add_argument("--log-level", default="warning", choices=["debug", "info", "warning", "error"])
|
|
178
|
+
parser.add_argument("--json", action="store_true")
|
|
179
|
+
|
|
180
|
+
try:
|
|
181
|
+
parsed = parser.parse_args(args)
|
|
182
|
+
except SystemExit:
|
|
183
|
+
return self.EXIT_ERROR
|
|
184
|
+
|
|
185
|
+
# Configure logging
|
|
186
|
+
log_levels = {
|
|
187
|
+
"debug": logging.DEBUG,
|
|
188
|
+
"info": logging.INFO,
|
|
189
|
+
"warning": logging.WARNING,
|
|
190
|
+
"error": logging.ERROR,
|
|
191
|
+
}
|
|
192
|
+
logging.basicConfig(level=log_levels.get(parsed.log_level, logging.WARNING), stream=sys.stderr)
|
|
193
|
+
|
|
194
|
+
try:
|
|
195
|
+
from .recipe_adapter import RecipeMCPAdapter, RecipeMCPConfig
|
|
196
|
+
|
|
197
|
+
# Create config
|
|
198
|
+
config = RecipeMCPConfig(
|
|
199
|
+
recipe_name=parsed.recipe_name,
|
|
200
|
+
safe_mode=not parsed.no_safe_mode,
|
|
201
|
+
expose_agent_tools=not parsed.no_expose_tools,
|
|
202
|
+
expose_prompts=not parsed.no_expose_prompts,
|
|
203
|
+
session_ttl=parsed.session_ttl,
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
# Create adapter and load
|
|
207
|
+
adapter = RecipeMCPAdapter(parsed.recipe_name, config)
|
|
208
|
+
adapter.load()
|
|
209
|
+
|
|
210
|
+
# Create server
|
|
211
|
+
server = adapter.to_mcp_server()
|
|
212
|
+
|
|
213
|
+
if parsed.transport == "stdio":
|
|
214
|
+
logger.info(f"Starting recipe MCP server '{parsed.recipe_name}' on STDIO transport")
|
|
215
|
+
server.run_stdio()
|
|
216
|
+
else:
|
|
217
|
+
if not parsed.json:
|
|
218
|
+
self._print_success(f"Starting recipe MCP server '{parsed.recipe_name}' on http://{parsed.host}:{parsed.port}{parsed.endpoint}")
|
|
219
|
+
|
|
220
|
+
server.run_http_stream(
|
|
221
|
+
host=parsed.host,
|
|
222
|
+
port=parsed.port,
|
|
223
|
+
endpoint=parsed.endpoint,
|
|
224
|
+
api_key=parsed.api_key,
|
|
225
|
+
session_ttl=parsed.session_ttl,
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
return self.EXIT_SUCCESS
|
|
229
|
+
|
|
230
|
+
except ValueError as e:
|
|
231
|
+
self._print_error(str(e))
|
|
232
|
+
return self.EXIT_ERROR
|
|
233
|
+
except ImportError as e:
|
|
234
|
+
self._print_error(f"Missing dependency: {e}")
|
|
235
|
+
return self.EXIT_ERROR
|
|
236
|
+
except KeyboardInterrupt:
|
|
237
|
+
logger.info("Server stopped by user")
|
|
238
|
+
return self.EXIT_SUCCESS
|
|
239
|
+
except Exception as e:
|
|
240
|
+
self._print_error(str(e))
|
|
241
|
+
return self.EXIT_ERROR
|
|
242
|
+
|
|
243
|
+
def cmd_list_recipes(self, args: List[str]) -> int:
|
|
244
|
+
"""List available recipes."""
|
|
245
|
+
parser = argparse.ArgumentParser(prog="praisonai mcp list-recipes")
|
|
246
|
+
parser.add_argument("--tags", default=None, help="Filter by tags (comma-separated)")
|
|
247
|
+
parser.add_argument("--source", default=None, choices=["local", "package", "all"])
|
|
248
|
+
parser.add_argument("--json", action="store_true")
|
|
249
|
+
|
|
250
|
+
try:
|
|
251
|
+
parsed = parser.parse_args(args)
|
|
252
|
+
except SystemExit:
|
|
253
|
+
return self.EXIT_ERROR
|
|
254
|
+
|
|
255
|
+
try:
|
|
256
|
+
from ..recipe.core import list_recipes
|
|
257
|
+
|
|
258
|
+
tags = parsed.tags.split(",") if parsed.tags else None
|
|
259
|
+
recipes = list_recipes(tags=tags, source=parsed.source)
|
|
260
|
+
|
|
261
|
+
if parsed.json:
|
|
262
|
+
self._print_json({"recipes": [r.to_dict() for r in recipes]})
|
|
263
|
+
else:
|
|
264
|
+
if not recipes:
|
|
265
|
+
print("No recipes found")
|
|
266
|
+
return self.EXIT_SUCCESS
|
|
267
|
+
|
|
268
|
+
print(f"\n[bold]Available Recipes ({len(recipes)}):[/bold]\n")
|
|
269
|
+
for recipe in recipes:
|
|
270
|
+
print(f" • {recipe.name} (v{recipe.version})")
|
|
271
|
+
print(f" {recipe.description}")
|
|
272
|
+
if recipe.tags:
|
|
273
|
+
print(f" Tags: {', '.join(recipe.tags)}")
|
|
274
|
+
print()
|
|
275
|
+
|
|
276
|
+
return self.EXIT_SUCCESS
|
|
277
|
+
|
|
278
|
+
except ImportError:
|
|
279
|
+
# Fallback to template discovery
|
|
280
|
+
try:
|
|
281
|
+
from ..templates.discovery import TemplateDiscovery
|
|
282
|
+
|
|
283
|
+
discovery = TemplateDiscovery()
|
|
284
|
+
templates = discovery.discover_all()
|
|
285
|
+
|
|
286
|
+
if parsed.json:
|
|
287
|
+
self._print_json({"recipes": [t.to_dict() for t in templates]})
|
|
288
|
+
else:
|
|
289
|
+
if not templates:
|
|
290
|
+
print("No recipes found")
|
|
291
|
+
return self.EXIT_SUCCESS
|
|
292
|
+
|
|
293
|
+
print(f"\n[bold]Available Recipes ({len(templates)}):[/bold]\n")
|
|
294
|
+
for template in templates:
|
|
295
|
+
print(f" • {template.name}")
|
|
296
|
+
if template.description:
|
|
297
|
+
print(f" {template.description}")
|
|
298
|
+
print()
|
|
299
|
+
|
|
300
|
+
return self.EXIT_SUCCESS
|
|
301
|
+
|
|
302
|
+
except Exception as e:
|
|
303
|
+
self._print_error(str(e))
|
|
304
|
+
return self.EXIT_ERROR
|
|
305
|
+
except Exception as e:
|
|
306
|
+
self._print_error(str(e))
|
|
307
|
+
return self.EXIT_ERROR
|
|
308
|
+
|
|
309
|
+
def cmd_validate_recipe(self, args: List[str]) -> int:
|
|
310
|
+
"""Validate recipe MCP compatibility."""
|
|
311
|
+
parser = argparse.ArgumentParser(prog="praisonai mcp validate-recipe")
|
|
312
|
+
parser.add_argument("recipe_name", help="Recipe name to validate")
|
|
313
|
+
parser.add_argument("--json", action="store_true")
|
|
314
|
+
|
|
315
|
+
try:
|
|
316
|
+
parsed = parser.parse_args(args)
|
|
317
|
+
except SystemExit:
|
|
318
|
+
return self.EXIT_ERROR
|
|
319
|
+
|
|
320
|
+
try:
|
|
321
|
+
from .recipe_adapter import RecipeMCPAdapter
|
|
322
|
+
|
|
323
|
+
errors = []
|
|
324
|
+
warnings = []
|
|
325
|
+
|
|
326
|
+
# Try to load the recipe
|
|
327
|
+
try:
|
|
328
|
+
adapter = RecipeMCPAdapter(parsed.recipe_name)
|
|
329
|
+
adapter.load()
|
|
330
|
+
except ValueError as e:
|
|
331
|
+
errors.append(f"Recipe not found: {e}")
|
|
332
|
+
except Exception as e:
|
|
333
|
+
errors.append(f"Failed to load recipe: {e}")
|
|
334
|
+
|
|
335
|
+
if not errors:
|
|
336
|
+
# Check tool registry
|
|
337
|
+
tools = adapter.get_tool_registry().list_all()
|
|
338
|
+
if not tools:
|
|
339
|
+
warnings.append("No tools registered")
|
|
340
|
+
|
|
341
|
+
# Check for dangerous tools
|
|
342
|
+
for tool in tools:
|
|
343
|
+
annotations = getattr(tool, 'annotations', {}) or {}
|
|
344
|
+
if annotations.get('type') == 'agent_tool':
|
|
345
|
+
original = annotations.get('original_tool', '')
|
|
346
|
+
if 'shell' in original.lower() or 'exec' in original.lower():
|
|
347
|
+
warnings.append(f"Tool '{tool.name}' may be dangerous (shell/exec)")
|
|
348
|
+
|
|
349
|
+
result = {
|
|
350
|
+
"valid": len(errors) == 0,
|
|
351
|
+
"recipe": parsed.recipe_name,
|
|
352
|
+
"errors": errors,
|
|
353
|
+
"warnings": warnings,
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if parsed.json:
|
|
357
|
+
self._print_json(result)
|
|
358
|
+
else:
|
|
359
|
+
if result["valid"]:
|
|
360
|
+
self._print_success(f"Recipe '{parsed.recipe_name}' is valid for MCP serving")
|
|
361
|
+
else:
|
|
362
|
+
self._print_error(f"Recipe '{parsed.recipe_name}' has validation errors")
|
|
363
|
+
|
|
364
|
+
if errors:
|
|
365
|
+
print("\n[bold red]Errors:[/bold red]")
|
|
366
|
+
for error in errors:
|
|
367
|
+
print(f" ✗ {error}")
|
|
368
|
+
|
|
369
|
+
if warnings:
|
|
370
|
+
print("\n[bold yellow]Warnings:[/bold yellow]")
|
|
371
|
+
for warning in warnings:
|
|
372
|
+
print(f" ⚠ {warning}")
|
|
373
|
+
|
|
374
|
+
return self.EXIT_SUCCESS if result["valid"] else self.EXIT_ERROR
|
|
375
|
+
|
|
376
|
+
except Exception as e:
|
|
377
|
+
self._print_error(str(e))
|
|
378
|
+
return self.EXIT_ERROR
|
|
379
|
+
|
|
380
|
+
def cmd_inspect_recipe(self, args: List[str]) -> int:
|
|
381
|
+
"""Inspect recipe MCP schema."""
|
|
382
|
+
parser = argparse.ArgumentParser(prog="praisonai mcp inspect-recipe")
|
|
383
|
+
parser.add_argument("recipe_name", help="Recipe name to inspect")
|
|
384
|
+
parser.add_argument("--tools", action="store_true", help="Show tools")
|
|
385
|
+
parser.add_argument("--resources", action="store_true", help="Show resources")
|
|
386
|
+
parser.add_argument("--prompts", action="store_true", help="Show prompts")
|
|
387
|
+
parser.add_argument("--metadata", action="store_true", help="Show metadata")
|
|
388
|
+
parser.add_argument("--json", action="store_true")
|
|
389
|
+
|
|
390
|
+
try:
|
|
391
|
+
parsed = parser.parse_args(args)
|
|
392
|
+
except SystemExit:
|
|
393
|
+
return self.EXIT_ERROR
|
|
394
|
+
|
|
395
|
+
# Default to showing all if nothing specified
|
|
396
|
+
show_all = not (parsed.tools or parsed.resources or parsed.prompts or parsed.metadata)
|
|
397
|
+
|
|
398
|
+
try:
|
|
399
|
+
from .recipe_adapter import RecipeMCPAdapter
|
|
400
|
+
|
|
401
|
+
adapter = RecipeMCPAdapter(parsed.recipe_name)
|
|
402
|
+
adapter.load()
|
|
403
|
+
|
|
404
|
+
result = {}
|
|
405
|
+
|
|
406
|
+
if show_all or parsed.metadata:
|
|
407
|
+
result["metadata"] = adapter.get_recipe_info()
|
|
408
|
+
|
|
409
|
+
if show_all or parsed.tools:
|
|
410
|
+
tools = adapter.get_tool_registry().list_schemas()
|
|
411
|
+
result["tools"] = tools
|
|
412
|
+
|
|
413
|
+
if show_all or parsed.resources:
|
|
414
|
+
resources = adapter.get_resource_registry().list_schemas()
|
|
415
|
+
result["resources"] = resources
|
|
416
|
+
|
|
417
|
+
if show_all or parsed.prompts:
|
|
418
|
+
prompts = adapter.get_prompt_registry().list_schemas()
|
|
419
|
+
result["prompts"] = prompts
|
|
420
|
+
|
|
421
|
+
if parsed.json:
|
|
422
|
+
self._print_json(result)
|
|
423
|
+
else:
|
|
424
|
+
print(f"\n[bold cyan]Recipe: {parsed.recipe_name}[/bold cyan]\n")
|
|
425
|
+
|
|
426
|
+
if "metadata" in result:
|
|
427
|
+
meta = result["metadata"]
|
|
428
|
+
print("[bold]Metadata:[/bold]")
|
|
429
|
+
print(f" Version: {meta.get('version', 'unknown')}")
|
|
430
|
+
print(f" Description: {meta.get('description', 'N/A')}")
|
|
431
|
+
if meta.get('tags'):
|
|
432
|
+
print(f" Tags: {', '.join(meta['tags'])}")
|
|
433
|
+
print()
|
|
434
|
+
|
|
435
|
+
if "tools" in result:
|
|
436
|
+
tools = result["tools"]
|
|
437
|
+
print(f"[bold]Tools ({len(tools)}):[/bold]")
|
|
438
|
+
for tool in tools:
|
|
439
|
+
print(f" • {tool['name']}")
|
|
440
|
+
print(f" {tool.get('description', 'No description')}")
|
|
441
|
+
print()
|
|
442
|
+
|
|
443
|
+
if "resources" in result:
|
|
444
|
+
resources = result["resources"]
|
|
445
|
+
print(f"[bold]Resources ({len(resources)}):[/bold]")
|
|
446
|
+
for res in resources:
|
|
447
|
+
print(f" • {res['uri']}")
|
|
448
|
+
print(f" {res.get('description', 'No description')}")
|
|
449
|
+
print()
|
|
450
|
+
|
|
451
|
+
if "prompts" in result:
|
|
452
|
+
prompts = result["prompts"]
|
|
453
|
+
print(f"[bold]Prompts ({len(prompts)}):[/bold]")
|
|
454
|
+
for prompt in prompts:
|
|
455
|
+
print(f" • {prompt['name']}")
|
|
456
|
+
print(f" {prompt.get('description', 'No description')}")
|
|
457
|
+
print()
|
|
458
|
+
|
|
459
|
+
return self.EXIT_SUCCESS
|
|
460
|
+
|
|
461
|
+
except Exception as e:
|
|
462
|
+
self._print_error(str(e))
|
|
463
|
+
return self.EXIT_ERROR
|
|
464
|
+
|
|
465
|
+
def cmd_config_generate_recipe(self, args: List[str]) -> int:
|
|
466
|
+
"""Generate client config for recipe MCP server."""
|
|
467
|
+
parser = argparse.ArgumentParser(prog="praisonai mcp config-generate-recipe")
|
|
468
|
+
parser.add_argument("recipe_name", help="Recipe name")
|
|
469
|
+
parser.add_argument("--client", default="claude-desktop",
|
|
470
|
+
choices=["claude-desktop", "cursor", "vscode", "windsurf", "generic"])
|
|
471
|
+
parser.add_argument("--transport", default="stdio", choices=["stdio", "http-stream"])
|
|
472
|
+
parser.add_argument("--host", default="127.0.0.1")
|
|
473
|
+
parser.add_argument("--port", type=int, default=8080)
|
|
474
|
+
parser.add_argument("--output", default=None, help="Output file path")
|
|
475
|
+
|
|
476
|
+
try:
|
|
477
|
+
parsed = parser.parse_args(args)
|
|
478
|
+
except SystemExit:
|
|
479
|
+
return self.EXIT_ERROR
|
|
480
|
+
|
|
481
|
+
# Generate config
|
|
482
|
+
if parsed.transport == "stdio":
|
|
483
|
+
server_config = {
|
|
484
|
+
"command": "praisonai",
|
|
485
|
+
"args": ["mcp", "serve-recipe", parsed.recipe_name, "--transport", "stdio"],
|
|
486
|
+
}
|
|
487
|
+
else:
|
|
488
|
+
server_config = {
|
|
489
|
+
"url": f"http://{parsed.host}:{parsed.port}/mcp",
|
|
490
|
+
"transport": "http-stream",
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
if parsed.client == "claude-desktop":
|
|
494
|
+
config = {
|
|
495
|
+
"mcpServers": {
|
|
496
|
+
parsed.recipe_name: server_config
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
elif parsed.client in ("cursor", "windsurf"):
|
|
500
|
+
config = {
|
|
501
|
+
"mcpServers": {
|
|
502
|
+
parsed.recipe_name: server_config
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
elif parsed.client == "vscode":
|
|
506
|
+
config = {
|
|
507
|
+
"mcp.servers": {
|
|
508
|
+
parsed.recipe_name: server_config
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
else: # generic
|
|
512
|
+
config = {
|
|
513
|
+
"server": {
|
|
514
|
+
"name": parsed.recipe_name,
|
|
515
|
+
**server_config
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
config_json = json.dumps(config, indent=2)
|
|
520
|
+
|
|
521
|
+
if parsed.output:
|
|
522
|
+
with open(parsed.output, 'w') as f:
|
|
523
|
+
f.write(config_json)
|
|
524
|
+
self._print_success(f"Config written to {parsed.output}")
|
|
525
|
+
else:
|
|
526
|
+
print(f"\n[bold]{parsed.client} Configuration for {parsed.recipe_name}:[/bold]\n")
|
|
527
|
+
print(config_json)
|
|
528
|
+
print()
|
|
529
|
+
|
|
530
|
+
return self.EXIT_SUCCESS
|
|
531
|
+
|
|
532
|
+
def cmd_auth(self, args: List[str]) -> int:
|
|
533
|
+
"""Handle auth subcommands."""
|
|
534
|
+
if not args:
|
|
535
|
+
print("Usage: praisonai mcp auth <subcommand>")
|
|
536
|
+
print("Subcommands: generate-key, validate, oidc-discover")
|
|
537
|
+
return self.EXIT_ERROR
|
|
538
|
+
|
|
539
|
+
subcommand = args[0]
|
|
540
|
+
remaining = args[1:]
|
|
541
|
+
|
|
542
|
+
if subcommand == "generate-key":
|
|
543
|
+
return self._auth_generate_key(remaining)
|
|
544
|
+
elif subcommand == "validate":
|
|
545
|
+
return self._auth_validate(remaining)
|
|
546
|
+
elif subcommand == "oidc-discover":
|
|
547
|
+
return self._auth_oidc_discover(remaining)
|
|
548
|
+
else:
|
|
549
|
+
self._print_error(f"Unknown auth subcommand: {subcommand}")
|
|
550
|
+
return self.EXIT_ERROR
|
|
551
|
+
|
|
552
|
+
def _auth_generate_key(self, args: List[str]) -> int:
|
|
553
|
+
"""Generate API key."""
|
|
554
|
+
parser = argparse.ArgumentParser(prog="praisonai mcp auth generate-key")
|
|
555
|
+
parser.add_argument("--name", default=None, help="Key name")
|
|
556
|
+
parser.add_argument("--scopes", default=None, help="Comma-separated scopes")
|
|
557
|
+
parser.add_argument("--expires-in", type=int, default=None, help="Expiration in seconds")
|
|
558
|
+
parser.add_argument("--json", action="store_true")
|
|
559
|
+
|
|
560
|
+
try:
|
|
561
|
+
parsed = parser.parse_args(args)
|
|
562
|
+
except SystemExit:
|
|
563
|
+
return self.EXIT_ERROR
|
|
564
|
+
|
|
565
|
+
try:
|
|
566
|
+
from .auth.api_key import APIKeyAuth
|
|
567
|
+
|
|
568
|
+
auth = APIKeyAuth()
|
|
569
|
+
scopes = parsed.scopes.split(",") if parsed.scopes else None
|
|
570
|
+
|
|
571
|
+
raw_key, api_key = auth.generate_key(
|
|
572
|
+
name=parsed.name,
|
|
573
|
+
scopes=scopes,
|
|
574
|
+
expires_in=parsed.expires_in,
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
if parsed.json:
|
|
578
|
+
self._print_json({
|
|
579
|
+
"key": raw_key,
|
|
580
|
+
"key_id": api_key.key_id,
|
|
581
|
+
"name": api_key.name,
|
|
582
|
+
"scopes": api_key.scopes,
|
|
583
|
+
"expires_at": api_key.expires_at,
|
|
584
|
+
})
|
|
585
|
+
else:
|
|
586
|
+
print("\n[bold green]Generated API Key:[/bold green]")
|
|
587
|
+
print(f" Key: {raw_key}")
|
|
588
|
+
print(f" ID: {api_key.key_id}")
|
|
589
|
+
if api_key.name:
|
|
590
|
+
print(f" Name: {api_key.name}")
|
|
591
|
+
if api_key.scopes:
|
|
592
|
+
print(f" Scopes: {', '.join(api_key.scopes)}")
|
|
593
|
+
if api_key.expires_at:
|
|
594
|
+
print(f" Expires: {api_key.expires_at}")
|
|
595
|
+
print("\n[yellow]Save this key securely - it cannot be retrieved later.[/yellow]")
|
|
596
|
+
|
|
597
|
+
return self.EXIT_SUCCESS
|
|
598
|
+
|
|
599
|
+
except Exception as e:
|
|
600
|
+
self._print_error(str(e))
|
|
601
|
+
return self.EXIT_ERROR
|
|
602
|
+
|
|
603
|
+
def _auth_validate(self, args: List[str]) -> int:
|
|
604
|
+
"""Validate API key."""
|
|
605
|
+
parser = argparse.ArgumentParser(prog="praisonai mcp auth validate")
|
|
606
|
+
parser.add_argument("key", help="API key to validate")
|
|
607
|
+
parser.add_argument("--json", action="store_true")
|
|
608
|
+
|
|
609
|
+
try:
|
|
610
|
+
parsed = parser.parse_args(args)
|
|
611
|
+
except SystemExit:
|
|
612
|
+
return self.EXIT_ERROR
|
|
613
|
+
|
|
614
|
+
try:
|
|
615
|
+
from .auth.api_key import APIKeyAuth
|
|
616
|
+
|
|
617
|
+
auth = APIKeyAuth()
|
|
618
|
+
is_valid, api_key = auth.validate(parsed.key)
|
|
619
|
+
|
|
620
|
+
if parsed.json:
|
|
621
|
+
self._print_json({
|
|
622
|
+
"valid": is_valid,
|
|
623
|
+
"key_id": api_key.key_id if api_key else None,
|
|
624
|
+
})
|
|
625
|
+
else:
|
|
626
|
+
if is_valid:
|
|
627
|
+
self._print_success("API key is valid")
|
|
628
|
+
else:
|
|
629
|
+
self._print_error("API key is invalid or expired")
|
|
630
|
+
|
|
631
|
+
return self.EXIT_SUCCESS if is_valid else self.EXIT_ERROR
|
|
632
|
+
|
|
633
|
+
except Exception as e:
|
|
634
|
+
self._print_error(str(e))
|
|
635
|
+
return self.EXIT_ERROR
|
|
636
|
+
|
|
637
|
+
def _auth_oidc_discover(self, args: List[str]) -> int:
|
|
638
|
+
"""Discover OIDC configuration."""
|
|
639
|
+
parser = argparse.ArgumentParser(prog="praisonai mcp auth oidc-discover")
|
|
640
|
+
parser.add_argument("issuer", help="OIDC issuer URL")
|
|
641
|
+
parser.add_argument("--json", action="store_true")
|
|
642
|
+
|
|
643
|
+
try:
|
|
644
|
+
parsed = parser.parse_args(args)
|
|
645
|
+
except SystemExit:
|
|
646
|
+
return self.EXIT_ERROR
|
|
647
|
+
|
|
648
|
+
try:
|
|
649
|
+
import asyncio
|
|
650
|
+
from .auth.oidc import OIDCDiscovery
|
|
651
|
+
|
|
652
|
+
discovery = OIDCDiscovery()
|
|
653
|
+
config = asyncio.run(discovery.discover(parsed.issuer))
|
|
654
|
+
|
|
655
|
+
if parsed.json:
|
|
656
|
+
self._print_json(config.to_dict())
|
|
657
|
+
else:
|
|
658
|
+
print(f"\n[bold]OIDC Configuration for {parsed.issuer}:[/bold]\n")
|
|
659
|
+
print(f" Issuer: {config.issuer}")
|
|
660
|
+
print(f" Authorization: {config.authorization_endpoint}")
|
|
661
|
+
print(f" Token: {config.token_endpoint}")
|
|
662
|
+
if config.userinfo_endpoint:
|
|
663
|
+
print(f" UserInfo: {config.userinfo_endpoint}")
|
|
664
|
+
if config.scopes_supported:
|
|
665
|
+
print(f" Scopes: {', '.join(config.scopes_supported[:5])}...")
|
|
666
|
+
|
|
667
|
+
return self.EXIT_SUCCESS
|
|
668
|
+
|
|
669
|
+
except Exception as e:
|
|
670
|
+
self._print_error(str(e))
|
|
671
|
+
return self.EXIT_ERROR
|
|
672
|
+
|
|
673
|
+
def cmd_tasks(self, args: List[str]) -> int:
|
|
674
|
+
"""Handle tasks subcommands."""
|
|
675
|
+
if not args:
|
|
676
|
+
print("Usage: praisonai mcp tasks <subcommand>")
|
|
677
|
+
print("Subcommands: list, get, cancel")
|
|
678
|
+
return self.EXIT_ERROR
|
|
679
|
+
|
|
680
|
+
subcommand = args[0]
|
|
681
|
+
remaining = args[1:]
|
|
682
|
+
|
|
683
|
+
if subcommand == "list":
|
|
684
|
+
return self._tasks_list(remaining)
|
|
685
|
+
elif subcommand == "get":
|
|
686
|
+
return self._tasks_get(remaining)
|
|
687
|
+
elif subcommand == "cancel":
|
|
688
|
+
return self._tasks_cancel(remaining)
|
|
689
|
+
else:
|
|
690
|
+
self._print_error(f"Unknown tasks subcommand: {subcommand}")
|
|
691
|
+
return self.EXIT_ERROR
|
|
692
|
+
|
|
693
|
+
def _tasks_list(self, args: List[str]) -> int:
|
|
694
|
+
"""List tasks."""
|
|
695
|
+
parser = argparse.ArgumentParser(prog="praisonai mcp tasks list")
|
|
696
|
+
parser.add_argument("--session", default=None, help="Filter by session ID")
|
|
697
|
+
parser.add_argument("--state", default=None, choices=["pending", "running", "completed", "failed", "cancelled"])
|
|
698
|
+
parser.add_argument("--limit", type=int, default=20)
|
|
699
|
+
parser.add_argument("--json", action="store_true")
|
|
700
|
+
|
|
701
|
+
try:
|
|
702
|
+
parsed = parser.parse_args(args)
|
|
703
|
+
except SystemExit:
|
|
704
|
+
return self.EXIT_ERROR
|
|
705
|
+
|
|
706
|
+
try:
|
|
707
|
+
from .tasks import get_task_manager, TaskState
|
|
708
|
+
|
|
709
|
+
manager = get_task_manager()
|
|
710
|
+
state = TaskState(parsed.state) if parsed.state else None
|
|
711
|
+
tasks = manager.list_tasks(
|
|
712
|
+
session_id=parsed.session,
|
|
713
|
+
state=state,
|
|
714
|
+
limit=parsed.limit,
|
|
715
|
+
)
|
|
716
|
+
|
|
717
|
+
if parsed.json:
|
|
718
|
+
self._print_json({"tasks": [t.to_dict() for t in tasks]})
|
|
719
|
+
else:
|
|
720
|
+
if not tasks:
|
|
721
|
+
print("No tasks found")
|
|
722
|
+
return self.EXIT_SUCCESS
|
|
723
|
+
|
|
724
|
+
print(f"\n[bold]Tasks ({len(tasks)}):[/bold]\n")
|
|
725
|
+
for task in tasks:
|
|
726
|
+
print(f" • {task.id} [{task.state.value}]")
|
|
727
|
+
print(f" Method: {task.method}")
|
|
728
|
+
if task.progress:
|
|
729
|
+
print(f" Progress: {task.progress.current}/{task.progress.total or '?'}")
|
|
730
|
+
print()
|
|
731
|
+
|
|
732
|
+
return self.EXIT_SUCCESS
|
|
733
|
+
|
|
734
|
+
except Exception as e:
|
|
735
|
+
self._print_error(str(e))
|
|
736
|
+
return self.EXIT_ERROR
|
|
737
|
+
|
|
738
|
+
def _tasks_get(self, args: List[str]) -> int:
|
|
739
|
+
"""Get task details."""
|
|
740
|
+
parser = argparse.ArgumentParser(prog="praisonai mcp tasks get")
|
|
741
|
+
parser.add_argument("task_id", help="Task ID")
|
|
742
|
+
parser.add_argument("--json", action="store_true")
|
|
743
|
+
|
|
744
|
+
try:
|
|
745
|
+
parsed = parser.parse_args(args)
|
|
746
|
+
except SystemExit:
|
|
747
|
+
return self.EXIT_ERROR
|
|
748
|
+
|
|
749
|
+
try:
|
|
750
|
+
from .tasks import get_task_manager
|
|
751
|
+
|
|
752
|
+
manager = get_task_manager()
|
|
753
|
+
task = manager.get_task(parsed.task_id)
|
|
754
|
+
|
|
755
|
+
if not task:
|
|
756
|
+
self._print_error(f"Task not found: {parsed.task_id}")
|
|
757
|
+
return self.EXIT_ERROR
|
|
758
|
+
|
|
759
|
+
if parsed.json:
|
|
760
|
+
self._print_json(task.to_dict())
|
|
761
|
+
else:
|
|
762
|
+
print(f"\n[bold]Task: {task.id}[/bold]\n")
|
|
763
|
+
print(f" State: {task.state.value}")
|
|
764
|
+
print(f" Method: {task.method}")
|
|
765
|
+
print(f" Created: {task.created_at}")
|
|
766
|
+
if task.progress:
|
|
767
|
+
print(f" Progress: {task.progress.current}/{task.progress.total or '?'}")
|
|
768
|
+
if task.result:
|
|
769
|
+
print(f" Result: {task.result}")
|
|
770
|
+
if task.error:
|
|
771
|
+
print(f" Error: {task.error}")
|
|
772
|
+
|
|
773
|
+
return self.EXIT_SUCCESS
|
|
774
|
+
|
|
775
|
+
except Exception as e:
|
|
776
|
+
self._print_error(str(e))
|
|
777
|
+
return self.EXIT_ERROR
|
|
778
|
+
|
|
779
|
+
def _tasks_cancel(self, args: List[str]) -> int:
|
|
780
|
+
"""Cancel a task."""
|
|
781
|
+
parser = argparse.ArgumentParser(prog="praisonai mcp tasks cancel")
|
|
782
|
+
parser.add_argument("task_id", help="Task ID")
|
|
783
|
+
parser.add_argument("--json", action="store_true")
|
|
784
|
+
|
|
785
|
+
try:
|
|
786
|
+
parsed = parser.parse_args(args)
|
|
787
|
+
except SystemExit:
|
|
788
|
+
return self.EXIT_ERROR
|
|
789
|
+
|
|
790
|
+
try:
|
|
791
|
+
import asyncio
|
|
792
|
+
from .tasks import get_task_manager
|
|
793
|
+
|
|
794
|
+
manager = get_task_manager()
|
|
795
|
+
task = asyncio.run(manager.cancel_task(parsed.task_id))
|
|
796
|
+
|
|
797
|
+
if not task:
|
|
798
|
+
self._print_error(f"Task not found: {parsed.task_id}")
|
|
799
|
+
return self.EXIT_ERROR
|
|
800
|
+
|
|
801
|
+
if parsed.json:
|
|
802
|
+
self._print_json({"cancelled": True, "task": task.to_dict()})
|
|
803
|
+
else:
|
|
804
|
+
self._print_success(f"Task {parsed.task_id} cancelled")
|
|
805
|
+
|
|
806
|
+
return self.EXIT_SUCCESS
|
|
807
|
+
|
|
808
|
+
except Exception as e:
|
|
809
|
+
self._print_error(str(e))
|
|
810
|
+
return self.EXIT_ERROR
|
|
811
|
+
|
|
812
|
+
|
|
813
|
+
def handle_recipe_mcp_command(args: List[str]) -> int:
|
|
814
|
+
"""
|
|
815
|
+
Entry point for recipe MCP CLI commands.
|
|
816
|
+
|
|
817
|
+
Args:
|
|
818
|
+
args: Command arguments
|
|
819
|
+
|
|
820
|
+
Returns:
|
|
821
|
+
Exit code
|
|
822
|
+
"""
|
|
823
|
+
cli = RecipeMCPCLI()
|
|
824
|
+
return cli.handle(args)
|