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,573 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Recipe MCP Adapter
|
|
3
|
+
|
|
4
|
+
Adapts PraisonAI recipes to MCP server primitives (tools, resources, prompts).
|
|
5
|
+
Enables any recipe to be served as an MCP server.
|
|
6
|
+
|
|
7
|
+
MCP Protocol Version: 2025-11-25
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import asyncio
|
|
11
|
+
import logging
|
|
12
|
+
import re
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Any, Callable, Dict, List, Optional, Set, TYPE_CHECKING, Union
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from .server import MCPServer
|
|
19
|
+
|
|
20
|
+
from .registry import (
|
|
21
|
+
MCPToolRegistry,
|
|
22
|
+
MCPResourceRegistry,
|
|
23
|
+
MCPPromptRegistry,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
# Default denied tools (dangerous by default)
|
|
29
|
+
DEFAULT_DENIED_TOOLS: Set[str] = {
|
|
30
|
+
"shell.exec",
|
|
31
|
+
"shell.run",
|
|
32
|
+
"shell_tool",
|
|
33
|
+
"file.write",
|
|
34
|
+
"file.delete",
|
|
35
|
+
"fs.write",
|
|
36
|
+
"fs.delete",
|
|
37
|
+
"network.unrestricted",
|
|
38
|
+
"db.write",
|
|
39
|
+
"db.delete",
|
|
40
|
+
"execute_command",
|
|
41
|
+
"os.system",
|
|
42
|
+
"subprocess.run",
|
|
43
|
+
"eval",
|
|
44
|
+
"exec",
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass
|
|
49
|
+
class RecipeMCPConfig:
|
|
50
|
+
"""Configuration for serving a recipe as MCP server."""
|
|
51
|
+
|
|
52
|
+
recipe_name: str
|
|
53
|
+
|
|
54
|
+
# Tool exposure settings
|
|
55
|
+
expose_agent_tools: bool = True
|
|
56
|
+
expose_run_tool: bool = True
|
|
57
|
+
tool_namespace: str = "prefixed" # "flat", "nested", or "prefixed"
|
|
58
|
+
|
|
59
|
+
# Resource exposure settings
|
|
60
|
+
expose_config: bool = True
|
|
61
|
+
expose_outputs: bool = True
|
|
62
|
+
|
|
63
|
+
# Prompt exposure settings
|
|
64
|
+
expose_prompts: bool = True
|
|
65
|
+
expose_agent_instructions: bool = True
|
|
66
|
+
|
|
67
|
+
# Security settings
|
|
68
|
+
safe_mode: bool = True
|
|
69
|
+
tool_allowlist: Optional[List[str]] = None
|
|
70
|
+
tool_denylist: Optional[List[str]] = None
|
|
71
|
+
workspace_path: Optional[str] = None
|
|
72
|
+
allow_network: bool = False
|
|
73
|
+
env_allowlist: Optional[List[str]] = None
|
|
74
|
+
|
|
75
|
+
# Server settings
|
|
76
|
+
server_name: Optional[str] = None
|
|
77
|
+
server_version: str = "1.0.0"
|
|
78
|
+
server_description: Optional[str] = None
|
|
79
|
+
server_icon: Optional[str] = None
|
|
80
|
+
|
|
81
|
+
# Session settings
|
|
82
|
+
session_ttl: int = 3600
|
|
83
|
+
max_concurrent_runs: int = 5
|
|
84
|
+
|
|
85
|
+
def __post_init__(self):
|
|
86
|
+
if self.server_name is None:
|
|
87
|
+
self.server_name = self.recipe_name
|
|
88
|
+
if self.tool_denylist is None:
|
|
89
|
+
self.tool_denylist = list(DEFAULT_DENIED_TOOLS)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@dataclass
|
|
93
|
+
class RecipeToolWrapper:
|
|
94
|
+
"""Wrapper for a recipe tool with MCP metadata."""
|
|
95
|
+
|
|
96
|
+
name: str
|
|
97
|
+
description: str
|
|
98
|
+
handler: Callable
|
|
99
|
+
input_schema: Dict[str, Any]
|
|
100
|
+
output_schema: Optional[Dict[str, Any]] = None
|
|
101
|
+
annotations: Optional[Dict[str, Any]] = None
|
|
102
|
+
icon: Optional[str] = None
|
|
103
|
+
agent_name: Optional[str] = None
|
|
104
|
+
original_name: Optional[str] = None
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class RecipeMCPAdapter:
|
|
108
|
+
"""
|
|
109
|
+
Adapts a recipe to MCP server primitives.
|
|
110
|
+
|
|
111
|
+
Maps:
|
|
112
|
+
- Recipe metadata → Server metadata
|
|
113
|
+
- Agent tools → MCP Tools (namespaced)
|
|
114
|
+
- Agent instructions → MCP Prompts
|
|
115
|
+
- Recipe config/outputs → MCP Resources
|
|
116
|
+
|
|
117
|
+
Example:
|
|
118
|
+
adapter = RecipeMCPAdapter("support-reply")
|
|
119
|
+
adapter.load()
|
|
120
|
+
server = adapter.to_mcp_server()
|
|
121
|
+
server.run(transport="stdio")
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
def __init__(
|
|
125
|
+
self,
|
|
126
|
+
recipe_name: str,
|
|
127
|
+
config: Optional[RecipeMCPConfig] = None,
|
|
128
|
+
):
|
|
129
|
+
"""
|
|
130
|
+
Initialize recipe adapter.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
recipe_name: Name of the recipe to adapt
|
|
134
|
+
config: Optional configuration (uses defaults if None)
|
|
135
|
+
"""
|
|
136
|
+
self.recipe_name = recipe_name
|
|
137
|
+
self.config = config or RecipeMCPConfig(recipe_name=recipe_name)
|
|
138
|
+
|
|
139
|
+
self._recipe_config = None
|
|
140
|
+
self._workflow_config = None
|
|
141
|
+
self._loaded = False
|
|
142
|
+
|
|
143
|
+
# Registries
|
|
144
|
+
self._tool_registry = MCPToolRegistry()
|
|
145
|
+
self._resource_registry = MCPResourceRegistry()
|
|
146
|
+
self._prompt_registry = MCPPromptRegistry()
|
|
147
|
+
|
|
148
|
+
# Runtime state
|
|
149
|
+
self._active_runs: Dict[str, Any] = {}
|
|
150
|
+
self._run_results: Dict[str, Any] = {}
|
|
151
|
+
|
|
152
|
+
def load(self) -> None:
|
|
153
|
+
"""Load recipe and build MCP registries."""
|
|
154
|
+
if self._loaded:
|
|
155
|
+
return
|
|
156
|
+
|
|
157
|
+
# Load recipe configuration
|
|
158
|
+
self._recipe_config = self._load_recipe_config()
|
|
159
|
+
if self._recipe_config is None:
|
|
160
|
+
raise ValueError(f"Recipe not found: {self.recipe_name}")
|
|
161
|
+
|
|
162
|
+
# Load workflow configuration
|
|
163
|
+
self._workflow_config = self._load_workflow_config()
|
|
164
|
+
|
|
165
|
+
# Build registries
|
|
166
|
+
self._build_tool_registry()
|
|
167
|
+
self._build_resource_registry()
|
|
168
|
+
self._build_prompt_registry()
|
|
169
|
+
|
|
170
|
+
self._loaded = True
|
|
171
|
+
logger.info(f"Loaded recipe '{self.recipe_name}' as MCP server")
|
|
172
|
+
|
|
173
|
+
def _load_recipe_config(self) -> Optional[Dict[str, Any]]:
|
|
174
|
+
"""Load recipe configuration from template."""
|
|
175
|
+
try:
|
|
176
|
+
from ..recipe.core import _load_recipe
|
|
177
|
+
recipe = _load_recipe(self.recipe_name, offline=False)
|
|
178
|
+
if recipe:
|
|
179
|
+
return recipe.to_dict() if hasattr(recipe, 'to_dict') else recipe.raw
|
|
180
|
+
except ImportError:
|
|
181
|
+
pass
|
|
182
|
+
|
|
183
|
+
# Fallback: try loading directly from templates
|
|
184
|
+
try:
|
|
185
|
+
from ..templates.discovery import TemplateDiscovery
|
|
186
|
+
discovery = TemplateDiscovery()
|
|
187
|
+
template = discovery.find_template(self.recipe_name)
|
|
188
|
+
if template:
|
|
189
|
+
return self._load_yaml_file(template.path)
|
|
190
|
+
except ImportError:
|
|
191
|
+
pass
|
|
192
|
+
|
|
193
|
+
return None
|
|
194
|
+
|
|
195
|
+
def _load_workflow_config(self) -> Optional[Dict[str, Any]]:
|
|
196
|
+
"""Load workflow configuration from recipe."""
|
|
197
|
+
if not self._recipe_config:
|
|
198
|
+
return None
|
|
199
|
+
|
|
200
|
+
# Check for workflow file reference
|
|
201
|
+
workflow_file = self._recipe_config.get("workflow")
|
|
202
|
+
if workflow_file:
|
|
203
|
+
recipe_path = self._recipe_config.get("path")
|
|
204
|
+
if recipe_path:
|
|
205
|
+
workflow_path = Path(recipe_path).parent / workflow_file
|
|
206
|
+
if workflow_path.exists():
|
|
207
|
+
return self._load_yaml_file(str(workflow_path))
|
|
208
|
+
|
|
209
|
+
# Check for inline workflow
|
|
210
|
+
if "agents" in self._recipe_config or "tasks" in self._recipe_config:
|
|
211
|
+
return self._recipe_config
|
|
212
|
+
|
|
213
|
+
return None
|
|
214
|
+
|
|
215
|
+
def _load_yaml_file(self, path: str) -> Optional[Dict[str, Any]]:
|
|
216
|
+
"""Load YAML file."""
|
|
217
|
+
try:
|
|
218
|
+
import yaml
|
|
219
|
+
with open(path, 'r') as f:
|
|
220
|
+
return yaml.safe_load(f)
|
|
221
|
+
except Exception as e:
|
|
222
|
+
logger.warning(f"Failed to load YAML file {path}: {e}")
|
|
223
|
+
return None
|
|
224
|
+
|
|
225
|
+
def _build_tool_registry(self) -> None:
|
|
226
|
+
"""Build tool registry from recipe."""
|
|
227
|
+
# Add recipe.run meta-tool
|
|
228
|
+
if self.config.expose_run_tool:
|
|
229
|
+
self._register_run_tool()
|
|
230
|
+
|
|
231
|
+
# Add agent tools (if enabled and safe)
|
|
232
|
+
if self.config.expose_agent_tools and self._workflow_config:
|
|
233
|
+
self._register_agent_tools()
|
|
234
|
+
|
|
235
|
+
def _register_run_tool(self) -> None:
|
|
236
|
+
"""Register the recipe.run meta-tool."""
|
|
237
|
+
recipe_name = self.recipe_name
|
|
238
|
+
description = self._recipe_config.get("description", f"Execute the {recipe_name} recipe")
|
|
239
|
+
|
|
240
|
+
# Build input schema from recipe config_schema
|
|
241
|
+
config_schema = self._recipe_config.get("config_schema", {})
|
|
242
|
+
input_schema = {
|
|
243
|
+
"type": "object",
|
|
244
|
+
"properties": {
|
|
245
|
+
"input": {
|
|
246
|
+
"type": "object",
|
|
247
|
+
"description": "Input data for the recipe",
|
|
248
|
+
},
|
|
249
|
+
"config": {
|
|
250
|
+
"type": "object",
|
|
251
|
+
"description": "Configuration overrides",
|
|
252
|
+
"properties": config_schema.get("properties", {}),
|
|
253
|
+
},
|
|
254
|
+
"session_id": {
|
|
255
|
+
"type": "string",
|
|
256
|
+
"description": "Optional session ID for state grouping",
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
"required": [],
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async def run_recipe_handler(
|
|
263
|
+
input: Optional[Dict[str, Any]] = None,
|
|
264
|
+
config: Optional[Dict[str, Any]] = None,
|
|
265
|
+
session_id: Optional[str] = None,
|
|
266
|
+
) -> Dict[str, Any]:
|
|
267
|
+
"""Execute the recipe."""
|
|
268
|
+
return await self._execute_recipe(input, config, session_id)
|
|
269
|
+
|
|
270
|
+
tool_name = self._namespace_tool(recipe_name, None, "run")
|
|
271
|
+
|
|
272
|
+
self._tool_registry.register(
|
|
273
|
+
name=tool_name,
|
|
274
|
+
handler=run_recipe_handler,
|
|
275
|
+
description=description,
|
|
276
|
+
input_schema=input_schema,
|
|
277
|
+
annotations={
|
|
278
|
+
"recipe": recipe_name,
|
|
279
|
+
"type": "recipe_run",
|
|
280
|
+
},
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
def _register_agent_tools(self) -> None:
|
|
284
|
+
"""Register individual agent tools."""
|
|
285
|
+
agents = self._workflow_config.get("agents", [])
|
|
286
|
+
|
|
287
|
+
for agent_config in agents:
|
|
288
|
+
agent_name = agent_config.get("name", "agent")
|
|
289
|
+
agent_tools = agent_config.get("tools", [])
|
|
290
|
+
|
|
291
|
+
for tool_spec in agent_tools:
|
|
292
|
+
tool_name = tool_spec if isinstance(tool_spec, str) else tool_spec.get("name", "")
|
|
293
|
+
|
|
294
|
+
# Check if tool is allowed
|
|
295
|
+
if not self._is_tool_allowed(tool_name):
|
|
296
|
+
logger.debug(f"Skipping denied tool: {tool_name}")
|
|
297
|
+
continue
|
|
298
|
+
|
|
299
|
+
# Create wrapper for the tool
|
|
300
|
+
self._register_agent_tool(agent_name, tool_name, tool_spec)
|
|
301
|
+
|
|
302
|
+
def _register_agent_tool(
|
|
303
|
+
self,
|
|
304
|
+
agent_name: str,
|
|
305
|
+
tool_name: str,
|
|
306
|
+
tool_spec: Union[str, Dict[str, Any]],
|
|
307
|
+
) -> None:
|
|
308
|
+
"""Register a single agent tool."""
|
|
309
|
+
# Get tool description
|
|
310
|
+
if isinstance(tool_spec, dict):
|
|
311
|
+
description = tool_spec.get("description", f"Tool: {tool_name}")
|
|
312
|
+
input_schema = tool_spec.get("input_schema", {"type": "object", "properties": {}})
|
|
313
|
+
else:
|
|
314
|
+
description = f"Tool: {tool_name}"
|
|
315
|
+
input_schema = {"type": "object", "properties": {}}
|
|
316
|
+
|
|
317
|
+
# Create namespaced tool name
|
|
318
|
+
namespaced_name = self._namespace_tool(self.recipe_name, agent_name, tool_name)
|
|
319
|
+
|
|
320
|
+
async def tool_handler(**kwargs) -> Dict[str, Any]:
|
|
321
|
+
"""Execute the agent tool."""
|
|
322
|
+
return await self._execute_agent_tool(agent_name, tool_name, kwargs)
|
|
323
|
+
|
|
324
|
+
self._tool_registry.register(
|
|
325
|
+
name=namespaced_name,
|
|
326
|
+
handler=tool_handler,
|
|
327
|
+
description=description,
|
|
328
|
+
input_schema=input_schema,
|
|
329
|
+
annotations={
|
|
330
|
+
"recipe": self.recipe_name,
|
|
331
|
+
"agent": agent_name,
|
|
332
|
+
"original_tool": tool_name,
|
|
333
|
+
"type": "agent_tool",
|
|
334
|
+
},
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
def _is_tool_allowed(self, tool_name: str) -> bool:
|
|
338
|
+
"""Check if a tool is allowed based on config."""
|
|
339
|
+
if not self.config.safe_mode:
|
|
340
|
+
return True
|
|
341
|
+
|
|
342
|
+
# Check explicit allowlist
|
|
343
|
+
if self.config.tool_allowlist:
|
|
344
|
+
return tool_name in self.config.tool_allowlist
|
|
345
|
+
|
|
346
|
+
# Check denylist
|
|
347
|
+
if self.config.tool_denylist:
|
|
348
|
+
for denied in self.config.tool_denylist:
|
|
349
|
+
if denied in tool_name or tool_name in denied:
|
|
350
|
+
return False
|
|
351
|
+
|
|
352
|
+
return True
|
|
353
|
+
|
|
354
|
+
def _namespace_tool(
|
|
355
|
+
self,
|
|
356
|
+
recipe_name: str,
|
|
357
|
+
agent_name: Optional[str],
|
|
358
|
+
tool_name: str,
|
|
359
|
+
) -> str:
|
|
360
|
+
"""Generate namespaced tool name per MCP 2025-11-25 guidance."""
|
|
361
|
+
# Sanitize names
|
|
362
|
+
recipe = self._sanitize_name(recipe_name)
|
|
363
|
+
tool = self._sanitize_name(tool_name)
|
|
364
|
+
|
|
365
|
+
if self.config.tool_namespace == "flat":
|
|
366
|
+
return tool
|
|
367
|
+
elif self.config.tool_namespace == "nested":
|
|
368
|
+
if agent_name:
|
|
369
|
+
agent = self._sanitize_name(agent_name)
|
|
370
|
+
return f"{recipe}/{agent}/{tool}"
|
|
371
|
+
return f"{recipe}/{tool}"
|
|
372
|
+
else: # prefixed (default)
|
|
373
|
+
if agent_name:
|
|
374
|
+
agent = self._sanitize_name(agent_name)
|
|
375
|
+
return f"{recipe}.{agent}.{tool}"
|
|
376
|
+
return f"{recipe}.{tool}"
|
|
377
|
+
|
|
378
|
+
def _sanitize_name(self, name: str) -> str:
|
|
379
|
+
"""Sanitize name for MCP tool naming."""
|
|
380
|
+
# Convert to lowercase, replace spaces/underscores with hyphens
|
|
381
|
+
name = name.lower().strip()
|
|
382
|
+
name = re.sub(r'[_\s]+', '-', name)
|
|
383
|
+
name = re.sub(r'[^a-z0-9\-]', '', name)
|
|
384
|
+
return name
|
|
385
|
+
|
|
386
|
+
def _build_resource_registry(self) -> None:
|
|
387
|
+
"""Build resource registry from recipe."""
|
|
388
|
+
if not self._recipe_config:
|
|
389
|
+
return
|
|
390
|
+
|
|
391
|
+
recipe_name = self.recipe_name
|
|
392
|
+
|
|
393
|
+
# Register config resource
|
|
394
|
+
if self.config.expose_config:
|
|
395
|
+
self._resource_registry.register(
|
|
396
|
+
uri=f"recipe://{recipe_name}/config",
|
|
397
|
+
handler=lambda: self._recipe_config,
|
|
398
|
+
name=f"{recipe_name}-config",
|
|
399
|
+
description=f"Configuration for {recipe_name} recipe",
|
|
400
|
+
mime_type="application/json",
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
# Register schema resource
|
|
404
|
+
config_schema = self._recipe_config.get("config_schema")
|
|
405
|
+
if config_schema:
|
|
406
|
+
self._resource_registry.register(
|
|
407
|
+
uri=f"recipe://{recipe_name}/schema",
|
|
408
|
+
handler=lambda: config_schema,
|
|
409
|
+
name=f"{recipe_name}-schema",
|
|
410
|
+
description=f"Input schema for {recipe_name} recipe",
|
|
411
|
+
mime_type="application/json",
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
# Register outputs resource
|
|
415
|
+
if self.config.expose_outputs:
|
|
416
|
+
outputs = self._recipe_config.get("outputs", [])
|
|
417
|
+
if outputs:
|
|
418
|
+
self._resource_registry.register(
|
|
419
|
+
uri=f"recipe://{recipe_name}/outputs",
|
|
420
|
+
handler=lambda: outputs,
|
|
421
|
+
name=f"{recipe_name}-outputs",
|
|
422
|
+
description=f"Output definitions for {recipe_name} recipe",
|
|
423
|
+
mime_type="application/json",
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
def _build_prompt_registry(self) -> None:
|
|
427
|
+
"""Build prompt registry from recipe."""
|
|
428
|
+
if not self.config.expose_prompts:
|
|
429
|
+
return
|
|
430
|
+
|
|
431
|
+
recipe_name = self.recipe_name
|
|
432
|
+
|
|
433
|
+
# Register recipe description as prompt
|
|
434
|
+
description = self._recipe_config.get("description", "")
|
|
435
|
+
if description:
|
|
436
|
+
self._prompt_registry.register(
|
|
437
|
+
name=f"{recipe_name}-description",
|
|
438
|
+
handler=lambda: description,
|
|
439
|
+
description=f"Description of {recipe_name} recipe",
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
# Register agent instructions as prompts
|
|
443
|
+
if self.config.expose_agent_instructions and self._workflow_config:
|
|
444
|
+
agents = self._workflow_config.get("agents", [])
|
|
445
|
+
for agent_config in agents:
|
|
446
|
+
agent_name = agent_config.get("name", "agent")
|
|
447
|
+
instructions = agent_config.get("instructions", agent_config.get("role", ""))
|
|
448
|
+
|
|
449
|
+
if instructions:
|
|
450
|
+
self._prompt_registry.register(
|
|
451
|
+
name=f"{recipe_name}-{self._sanitize_name(agent_name)}-instructions",
|
|
452
|
+
handler=lambda inst=instructions: inst,
|
|
453
|
+
description=f"Instructions for {agent_name} agent in {recipe_name}",
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
async def _execute_recipe(
|
|
457
|
+
self,
|
|
458
|
+
input: Optional[Dict[str, Any]] = None,
|
|
459
|
+
config: Optional[Dict[str, Any]] = None,
|
|
460
|
+
session_id: Optional[str] = None,
|
|
461
|
+
) -> Dict[str, Any]:
|
|
462
|
+
"""Execute the recipe."""
|
|
463
|
+
try:
|
|
464
|
+
from ..recipe.core import run as recipe_run
|
|
465
|
+
|
|
466
|
+
result = recipe_run(
|
|
467
|
+
name=self.recipe_name,
|
|
468
|
+
input=input or {},
|
|
469
|
+
config=config,
|
|
470
|
+
session_id=session_id,
|
|
471
|
+
)
|
|
472
|
+
|
|
473
|
+
return result.to_dict() if hasattr(result, 'to_dict') else {"output": result}
|
|
474
|
+
|
|
475
|
+
except ImportError:
|
|
476
|
+
return {"error": "Recipe execution not available", "status": "failed"}
|
|
477
|
+
except Exception as e:
|
|
478
|
+
logger.exception(f"Recipe execution failed: {e}")
|
|
479
|
+
return {"error": str(e), "status": "failed"}
|
|
480
|
+
|
|
481
|
+
async def _execute_agent_tool(
|
|
482
|
+
self,
|
|
483
|
+
agent_name: str,
|
|
484
|
+
tool_name: str,
|
|
485
|
+
arguments: Dict[str, Any],
|
|
486
|
+
) -> Dict[str, Any]:
|
|
487
|
+
"""Execute an agent tool directly."""
|
|
488
|
+
try:
|
|
489
|
+
# Try to load and execute the tool
|
|
490
|
+
from praisonaiagents.tools import get_tool
|
|
491
|
+
|
|
492
|
+
tool = get_tool(tool_name)
|
|
493
|
+
if tool:
|
|
494
|
+
if asyncio.iscoroutinefunction(tool):
|
|
495
|
+
result = await tool(**arguments)
|
|
496
|
+
else:
|
|
497
|
+
result = tool(**arguments)
|
|
498
|
+
return {"result": result, "status": "success"}
|
|
499
|
+
|
|
500
|
+
return {"error": f"Tool not found: {tool_name}", "status": "failed"}
|
|
501
|
+
|
|
502
|
+
except ImportError:
|
|
503
|
+
return {"error": "Tool execution not available", "status": "failed"}
|
|
504
|
+
except Exception as e:
|
|
505
|
+
logger.exception(f"Tool execution failed: {e}")
|
|
506
|
+
return {"error": str(e), "status": "failed"}
|
|
507
|
+
|
|
508
|
+
def to_mcp_server(self) -> "MCPServer":
|
|
509
|
+
"""Create MCPServer instance from this adapter."""
|
|
510
|
+
if not self._loaded:
|
|
511
|
+
self.load()
|
|
512
|
+
|
|
513
|
+
from .server import MCPServer
|
|
514
|
+
|
|
515
|
+
return MCPServer(
|
|
516
|
+
name=self.config.server_name or self.recipe_name,
|
|
517
|
+
version=self.config.server_version,
|
|
518
|
+
tool_registry=self._tool_registry,
|
|
519
|
+
resource_registry=self._resource_registry,
|
|
520
|
+
prompt_registry=self._prompt_registry,
|
|
521
|
+
instructions=self._recipe_config.get("description", ""),
|
|
522
|
+
)
|
|
523
|
+
|
|
524
|
+
def get_tool_registry(self) -> MCPToolRegistry:
|
|
525
|
+
"""Get the tool registry."""
|
|
526
|
+
return self._tool_registry
|
|
527
|
+
|
|
528
|
+
def get_resource_registry(self) -> MCPResourceRegistry:
|
|
529
|
+
"""Get the resource registry."""
|
|
530
|
+
return self._resource_registry
|
|
531
|
+
|
|
532
|
+
def get_prompt_registry(self) -> MCPPromptRegistry:
|
|
533
|
+
"""Get the prompt registry."""
|
|
534
|
+
return self._prompt_registry
|
|
535
|
+
|
|
536
|
+
def get_recipe_info(self) -> Dict[str, Any]:
|
|
537
|
+
"""Get recipe information."""
|
|
538
|
+
if not self._loaded:
|
|
539
|
+
self.load()
|
|
540
|
+
|
|
541
|
+
return {
|
|
542
|
+
"name": self.recipe_name,
|
|
543
|
+
"version": self._recipe_config.get("version", "1.0.0"),
|
|
544
|
+
"description": self._recipe_config.get("description", ""),
|
|
545
|
+
"author": self._recipe_config.get("author"),
|
|
546
|
+
"tags": self._recipe_config.get("tags", []),
|
|
547
|
+
"tools_count": len(self._tool_registry.list_all()),
|
|
548
|
+
"resources_count": len(self._resource_registry.list_all()),
|
|
549
|
+
"prompts_count": len(self._prompt_registry.list_all()),
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
def create_recipe_mcp_server(
|
|
554
|
+
recipe_name: str,
|
|
555
|
+
config: Optional[RecipeMCPConfig] = None,
|
|
556
|
+
) -> "MCPServer":
|
|
557
|
+
"""
|
|
558
|
+
Create an MCP server from a recipe.
|
|
559
|
+
|
|
560
|
+
Args:
|
|
561
|
+
recipe_name: Name of the recipe
|
|
562
|
+
config: Optional configuration
|
|
563
|
+
|
|
564
|
+
Returns:
|
|
565
|
+
MCPServer instance ready to run
|
|
566
|
+
|
|
567
|
+
Example:
|
|
568
|
+
server = create_recipe_mcp_server("support-reply")
|
|
569
|
+
server.run(transport="stdio")
|
|
570
|
+
"""
|
|
571
|
+
adapter = RecipeMCPAdapter(recipe_name, config)
|
|
572
|
+
adapter.load()
|
|
573
|
+
return adapter.to_mcp_server()
|