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,703 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP Tool/Resource/Prompt Registry
|
|
3
|
+
|
|
4
|
+
Provides a centralized registry for MCP tools, resources, and prompts.
|
|
5
|
+
Supports lazy registration, schema generation, pagination, and search.
|
|
6
|
+
|
|
7
|
+
MCP Protocol Version: 2025-11-25
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import base64
|
|
11
|
+
import inspect
|
|
12
|
+
import logging
|
|
13
|
+
from dataclasses import dataclass, field
|
|
14
|
+
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# Default page size for pagination
|
|
20
|
+
DEFAULT_PAGE_SIZE = 50
|
|
21
|
+
MAX_PAGE_SIZE = 100
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def encode_cursor(offset: int, snapshot_hash: Optional[str] = None) -> str:
|
|
25
|
+
"""Encode pagination cursor as base64url."""
|
|
26
|
+
data = f"{offset}"
|
|
27
|
+
if snapshot_hash:
|
|
28
|
+
data = f"{offset}:{snapshot_hash}"
|
|
29
|
+
return base64.urlsafe_b64encode(data.encode()).decode().rstrip("=")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def decode_cursor(cursor: str) -> Tuple[int, Optional[str]]:
|
|
33
|
+
"""
|
|
34
|
+
Decode pagination cursor from base64url.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
Tuple of (offset, snapshot_hash)
|
|
38
|
+
|
|
39
|
+
Raises:
|
|
40
|
+
ValueError: If cursor is invalid
|
|
41
|
+
"""
|
|
42
|
+
try:
|
|
43
|
+
# Add padding if needed
|
|
44
|
+
padding = 4 - len(cursor) % 4
|
|
45
|
+
if padding != 4:
|
|
46
|
+
cursor += "=" * padding
|
|
47
|
+
data = base64.urlsafe_b64decode(cursor).decode()
|
|
48
|
+
if ":" in data:
|
|
49
|
+
parts = data.split(":", 1)
|
|
50
|
+
return int(parts[0]), parts[1]
|
|
51
|
+
return int(data), None
|
|
52
|
+
except Exception as e:
|
|
53
|
+
raise ValueError(f"Invalid cursor: {e}")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass
|
|
57
|
+
class MCPToolDefinition:
|
|
58
|
+
"""Definition of an MCP tool with MCP 2025-11-25 annotations."""
|
|
59
|
+
name: str
|
|
60
|
+
description: str
|
|
61
|
+
handler: Callable
|
|
62
|
+
input_schema: Dict[str, Any]
|
|
63
|
+
output_schema: Optional[Dict[str, Any]] = None
|
|
64
|
+
annotations: Optional[Dict[str, Any]] = None
|
|
65
|
+
# MCP 2025-11-25 tool annotation hints
|
|
66
|
+
title: Optional[str] = None
|
|
67
|
+
category: Optional[str] = None
|
|
68
|
+
tags: Optional[List[str]] = None
|
|
69
|
+
# Behavior hints per MCP 2025-11-25 spec
|
|
70
|
+
read_only_hint: bool = False
|
|
71
|
+
destructive_hint: bool = True
|
|
72
|
+
idempotent_hint: bool = False
|
|
73
|
+
open_world_hint: bool = True
|
|
74
|
+
|
|
75
|
+
def to_mcp_schema(self) -> Dict[str, Any]:
|
|
76
|
+
"""Convert to MCP tool schema format per MCP 2025-11-25 spec."""
|
|
77
|
+
schema = {
|
|
78
|
+
"name": self.name,
|
|
79
|
+
"description": self.description,
|
|
80
|
+
"inputSchema": self.input_schema,
|
|
81
|
+
}
|
|
82
|
+
if self.output_schema:
|
|
83
|
+
schema["outputSchema"] = self.output_schema
|
|
84
|
+
|
|
85
|
+
# Build annotations per MCP 2025-11-25 spec
|
|
86
|
+
annotations = self.annotations.copy() if self.annotations else {}
|
|
87
|
+
|
|
88
|
+
# Add behavior hints
|
|
89
|
+
annotations["readOnlyHint"] = self.read_only_hint
|
|
90
|
+
annotations["destructiveHint"] = self.destructive_hint
|
|
91
|
+
annotations["idempotentHint"] = self.idempotent_hint
|
|
92
|
+
annotations["openWorldHint"] = self.open_world_hint
|
|
93
|
+
|
|
94
|
+
# Add title if present
|
|
95
|
+
if self.title:
|
|
96
|
+
annotations["title"] = self.title
|
|
97
|
+
|
|
98
|
+
schema["annotations"] = annotations
|
|
99
|
+
return schema
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@dataclass
|
|
103
|
+
class MCPResourceDefinition:
|
|
104
|
+
"""Definition of an MCP resource."""
|
|
105
|
+
uri: str
|
|
106
|
+
name: str
|
|
107
|
+
description: str
|
|
108
|
+
handler: Callable
|
|
109
|
+
mime_type: str = "application/json"
|
|
110
|
+
|
|
111
|
+
def to_mcp_schema(self) -> Dict[str, Any]:
|
|
112
|
+
"""Convert to MCP resource schema format."""
|
|
113
|
+
return {
|
|
114
|
+
"uri": self.uri,
|
|
115
|
+
"name": self.name,
|
|
116
|
+
"description": self.description,
|
|
117
|
+
"mimeType": self.mime_type,
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@dataclass
|
|
122
|
+
class MCPPromptDefinition:
|
|
123
|
+
"""Definition of an MCP prompt."""
|
|
124
|
+
name: str
|
|
125
|
+
description: str
|
|
126
|
+
handler: Callable
|
|
127
|
+
arguments: List[Dict[str, Any]] = field(default_factory=list)
|
|
128
|
+
|
|
129
|
+
def to_mcp_schema(self) -> Dict[str, Any]:
|
|
130
|
+
"""Convert to MCP prompt schema format."""
|
|
131
|
+
return {
|
|
132
|
+
"name": self.name,
|
|
133
|
+
"description": self.description,
|
|
134
|
+
"arguments": self.arguments,
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class MCPToolRegistry:
|
|
139
|
+
"""Registry for MCP tools."""
|
|
140
|
+
|
|
141
|
+
def __init__(self):
|
|
142
|
+
self._tools: Dict[str, MCPToolDefinition] = {}
|
|
143
|
+
self._lazy_loaders: List[Callable[[], None]] = []
|
|
144
|
+
|
|
145
|
+
def register(
|
|
146
|
+
self,
|
|
147
|
+
name: str,
|
|
148
|
+
handler: Callable,
|
|
149
|
+
description: Optional[str] = None,
|
|
150
|
+
input_schema: Optional[Dict[str, Any]] = None,
|
|
151
|
+
output_schema: Optional[Dict[str, Any]] = None,
|
|
152
|
+
annotations: Optional[Dict[str, Any]] = None,
|
|
153
|
+
) -> None:
|
|
154
|
+
"""Register a tool."""
|
|
155
|
+
if description is None:
|
|
156
|
+
description = inspect.getdoc(handler) or f"Tool: {name}"
|
|
157
|
+
if "\n" in description:
|
|
158
|
+
description = description.split("\n")[0]
|
|
159
|
+
|
|
160
|
+
if input_schema is None:
|
|
161
|
+
input_schema = self._generate_input_schema(handler)
|
|
162
|
+
|
|
163
|
+
self._tools[name] = MCPToolDefinition(
|
|
164
|
+
name=name,
|
|
165
|
+
description=description,
|
|
166
|
+
handler=handler,
|
|
167
|
+
input_schema=input_schema,
|
|
168
|
+
output_schema=output_schema,
|
|
169
|
+
annotations=annotations,
|
|
170
|
+
)
|
|
171
|
+
logger.debug(f"Registered MCP tool: {name}")
|
|
172
|
+
|
|
173
|
+
def register_lazy(self, loader: Callable[[], None]) -> None:
|
|
174
|
+
"""Register a lazy loader that will be called before listing tools."""
|
|
175
|
+
self._lazy_loaders.append(loader)
|
|
176
|
+
|
|
177
|
+
def _ensure_loaded(self) -> None:
|
|
178
|
+
"""Ensure all lazy loaders have been called."""
|
|
179
|
+
for loader in self._lazy_loaders:
|
|
180
|
+
try:
|
|
181
|
+
loader()
|
|
182
|
+
except Exception as e:
|
|
183
|
+
logger.warning(f"Lazy loader failed: {e}")
|
|
184
|
+
self._lazy_loaders.clear()
|
|
185
|
+
|
|
186
|
+
def get(self, name: str) -> Optional[MCPToolDefinition]:
|
|
187
|
+
"""Get a tool by name."""
|
|
188
|
+
self._ensure_loaded()
|
|
189
|
+
return self._tools.get(name)
|
|
190
|
+
|
|
191
|
+
def list_all(self) -> List[MCPToolDefinition]:
|
|
192
|
+
"""List all registered tools."""
|
|
193
|
+
self._ensure_loaded()
|
|
194
|
+
return list(self._tools.values())
|
|
195
|
+
|
|
196
|
+
def list_schemas(self) -> List[Dict[str, Any]]:
|
|
197
|
+
"""List all tool schemas."""
|
|
198
|
+
return [tool.to_mcp_schema() for tool in self.list_all()]
|
|
199
|
+
|
|
200
|
+
def list_paginated(
|
|
201
|
+
self,
|
|
202
|
+
cursor: Optional[str] = None,
|
|
203
|
+
page_size: int = DEFAULT_PAGE_SIZE,
|
|
204
|
+
) -> Tuple[List[Dict[str, Any]], Optional[str]]:
|
|
205
|
+
"""
|
|
206
|
+
List tool schemas with pagination per MCP 2025-11-25 spec.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
cursor: Opaque cursor from previous response
|
|
210
|
+
page_size: Number of items per page (server-determined, max 100)
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
Tuple of (tools list, nextCursor or None if no more results)
|
|
214
|
+
|
|
215
|
+
Raises:
|
|
216
|
+
ValueError: If cursor is invalid
|
|
217
|
+
"""
|
|
218
|
+
all_tools = self.list_all()
|
|
219
|
+
total = len(all_tools)
|
|
220
|
+
|
|
221
|
+
# Decode cursor to get offset
|
|
222
|
+
offset = 0
|
|
223
|
+
if cursor:
|
|
224
|
+
offset, _ = decode_cursor(cursor)
|
|
225
|
+
if offset < 0 or offset >= total:
|
|
226
|
+
raise ValueError(f"Invalid cursor: offset {offset} out of range")
|
|
227
|
+
|
|
228
|
+
# Clamp page size
|
|
229
|
+
page_size = min(page_size, MAX_PAGE_SIZE)
|
|
230
|
+
|
|
231
|
+
# Get page
|
|
232
|
+
end = min(offset + page_size, total)
|
|
233
|
+
page = all_tools[offset:end]
|
|
234
|
+
schemas = [tool.to_mcp_schema() for tool in page]
|
|
235
|
+
|
|
236
|
+
# Generate next cursor if more results
|
|
237
|
+
next_cursor = None
|
|
238
|
+
if end < total:
|
|
239
|
+
next_cursor = encode_cursor(end)
|
|
240
|
+
|
|
241
|
+
return schemas, next_cursor
|
|
242
|
+
|
|
243
|
+
def search(
|
|
244
|
+
self,
|
|
245
|
+
query: Optional[str] = None,
|
|
246
|
+
category: Optional[str] = None,
|
|
247
|
+
tags: Optional[List[str]] = None,
|
|
248
|
+
read_only: Optional[bool] = None,
|
|
249
|
+
cursor: Optional[str] = None,
|
|
250
|
+
page_size: int = DEFAULT_PAGE_SIZE,
|
|
251
|
+
) -> Tuple[List[Dict[str, Any]], Optional[str], int]:
|
|
252
|
+
"""
|
|
253
|
+
Search tools with filtering and pagination.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
query: Text to search in name, description, tags
|
|
257
|
+
category: Filter by category
|
|
258
|
+
tags: Filter by tags (any match)
|
|
259
|
+
read_only: Filter by readOnlyHint
|
|
260
|
+
cursor: Pagination cursor
|
|
261
|
+
page_size: Items per page
|
|
262
|
+
|
|
263
|
+
Returns:
|
|
264
|
+
Tuple of (tools list, nextCursor, total_count)
|
|
265
|
+
"""
|
|
266
|
+
all_tools = self.list_all()
|
|
267
|
+
|
|
268
|
+
# Apply filters
|
|
269
|
+
filtered = []
|
|
270
|
+
query_lower = query.lower() if query else None
|
|
271
|
+
|
|
272
|
+
for tool in all_tools:
|
|
273
|
+
# Query filter
|
|
274
|
+
if query_lower:
|
|
275
|
+
searchable = f"{tool.name} {tool.description}".lower()
|
|
276
|
+
if tool.tags:
|
|
277
|
+
searchable += " " + " ".join(tool.tags).lower()
|
|
278
|
+
if tool.category:
|
|
279
|
+
searchable += " " + tool.category.lower()
|
|
280
|
+
if query_lower not in searchable:
|
|
281
|
+
continue
|
|
282
|
+
|
|
283
|
+
# Category filter
|
|
284
|
+
if category and tool.category != category:
|
|
285
|
+
continue
|
|
286
|
+
|
|
287
|
+
# Tags filter (any match)
|
|
288
|
+
if tags:
|
|
289
|
+
tool_tags = set(tool.tags or [])
|
|
290
|
+
if not tool_tags.intersection(set(tags)):
|
|
291
|
+
continue
|
|
292
|
+
|
|
293
|
+
# Read-only filter
|
|
294
|
+
if read_only is not None and tool.read_only_hint != read_only:
|
|
295
|
+
continue
|
|
296
|
+
|
|
297
|
+
filtered.append(tool)
|
|
298
|
+
|
|
299
|
+
total = len(filtered)
|
|
300
|
+
|
|
301
|
+
# Pagination
|
|
302
|
+
offset = 0
|
|
303
|
+
if cursor:
|
|
304
|
+
offset, _ = decode_cursor(cursor)
|
|
305
|
+
if offset < 0:
|
|
306
|
+
offset = 0
|
|
307
|
+
|
|
308
|
+
page_size = min(page_size, MAX_PAGE_SIZE)
|
|
309
|
+
end = min(offset + page_size, total)
|
|
310
|
+
page = filtered[offset:end]
|
|
311
|
+
schemas = [tool.to_mcp_schema() for tool in page]
|
|
312
|
+
|
|
313
|
+
# Next cursor
|
|
314
|
+
next_cursor = None
|
|
315
|
+
if end < total:
|
|
316
|
+
next_cursor = encode_cursor(end)
|
|
317
|
+
|
|
318
|
+
return schemas, next_cursor, total
|
|
319
|
+
|
|
320
|
+
def _generate_input_schema(self, handler: Callable) -> Dict[str, Any]:
|
|
321
|
+
"""Generate JSON schema from function signature."""
|
|
322
|
+
sig = inspect.signature(handler)
|
|
323
|
+
hints = getattr(handler, "__annotations__", {})
|
|
324
|
+
|
|
325
|
+
properties = {}
|
|
326
|
+
required = []
|
|
327
|
+
|
|
328
|
+
for param_name, param in sig.parameters.items():
|
|
329
|
+
if param_name in ("self", "cls"):
|
|
330
|
+
continue
|
|
331
|
+
|
|
332
|
+
prop = {"type": "string"} # Default
|
|
333
|
+
|
|
334
|
+
# Get type hint
|
|
335
|
+
hint = hints.get(param_name)
|
|
336
|
+
if hint:
|
|
337
|
+
prop = self._type_to_json_schema(hint)
|
|
338
|
+
|
|
339
|
+
# Get description from docstring if available
|
|
340
|
+
docstring = inspect.getdoc(handler)
|
|
341
|
+
if docstring:
|
|
342
|
+
# Try to extract param description from docstring
|
|
343
|
+
for line in docstring.split("\n"):
|
|
344
|
+
if f":param {param_name}:" in line:
|
|
345
|
+
# Extract description after the param marker
|
|
346
|
+
parts = line.split(f":param {param_name}:", 1)
|
|
347
|
+
if len(parts) > 1:
|
|
348
|
+
prop["description"] = parts[1].strip()
|
|
349
|
+
|
|
350
|
+
properties[param_name] = prop
|
|
351
|
+
|
|
352
|
+
if param.default is inspect.Parameter.empty:
|
|
353
|
+
required.append(param_name)
|
|
354
|
+
|
|
355
|
+
return {
|
|
356
|
+
"type": "object",
|
|
357
|
+
"properties": properties,
|
|
358
|
+
"required": required if required else None,
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
def _type_to_json_schema(self, hint: Any) -> Dict[str, Any]:
|
|
362
|
+
"""Convert Python type hint to JSON schema."""
|
|
363
|
+
if hint is str:
|
|
364
|
+
return {"type": "string"}
|
|
365
|
+
elif hint is int:
|
|
366
|
+
return {"type": "integer"}
|
|
367
|
+
elif hint is float:
|
|
368
|
+
return {"type": "number"}
|
|
369
|
+
elif hint is bool:
|
|
370
|
+
return {"type": "boolean"}
|
|
371
|
+
elif hint is list or (hasattr(hint, "__origin__") and hint.__origin__ is list):
|
|
372
|
+
return {"type": "array", "items": {"type": "string"}}
|
|
373
|
+
elif hint is dict or (hasattr(hint, "__origin__") and hint.__origin__ is dict):
|
|
374
|
+
return {"type": "object"}
|
|
375
|
+
else:
|
|
376
|
+
return {"type": "string"}
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
class MCPResourceRegistry:
|
|
380
|
+
"""Registry for MCP resources."""
|
|
381
|
+
|
|
382
|
+
def __init__(self):
|
|
383
|
+
self._resources: Dict[str, MCPResourceDefinition] = {}
|
|
384
|
+
self._lazy_loaders: List[Callable[[], None]] = []
|
|
385
|
+
|
|
386
|
+
def register(
|
|
387
|
+
self,
|
|
388
|
+
uri: str,
|
|
389
|
+
handler: Callable,
|
|
390
|
+
name: Optional[str] = None,
|
|
391
|
+
description: Optional[str] = None,
|
|
392
|
+
mime_type: str = "application/json",
|
|
393
|
+
) -> None:
|
|
394
|
+
"""Register a resource."""
|
|
395
|
+
if name is None:
|
|
396
|
+
name = uri.split("/")[-1] or uri
|
|
397
|
+
if description is None:
|
|
398
|
+
description = inspect.getdoc(handler) or f"Resource: {name}"
|
|
399
|
+
if "\n" in description:
|
|
400
|
+
description = description.split("\n")[0]
|
|
401
|
+
|
|
402
|
+
self._resources[uri] = MCPResourceDefinition(
|
|
403
|
+
uri=uri,
|
|
404
|
+
name=name,
|
|
405
|
+
description=description,
|
|
406
|
+
handler=handler,
|
|
407
|
+
mime_type=mime_type,
|
|
408
|
+
)
|
|
409
|
+
logger.debug(f"Registered MCP resource: {uri}")
|
|
410
|
+
|
|
411
|
+
def register_lazy(self, loader: Callable[[], None]) -> None:
|
|
412
|
+
"""Register a lazy loader."""
|
|
413
|
+
self._lazy_loaders.append(loader)
|
|
414
|
+
|
|
415
|
+
def _ensure_loaded(self) -> None:
|
|
416
|
+
"""Ensure all lazy loaders have been called."""
|
|
417
|
+
for loader in self._lazy_loaders:
|
|
418
|
+
try:
|
|
419
|
+
loader()
|
|
420
|
+
except Exception as e:
|
|
421
|
+
logger.warning(f"Lazy loader failed: {e}")
|
|
422
|
+
self._lazy_loaders.clear()
|
|
423
|
+
|
|
424
|
+
def get(self, uri: str) -> Optional[MCPResourceDefinition]:
|
|
425
|
+
"""Get a resource by URI."""
|
|
426
|
+
self._ensure_loaded()
|
|
427
|
+
return self._resources.get(uri)
|
|
428
|
+
|
|
429
|
+
def list_all(self) -> List[MCPResourceDefinition]:
|
|
430
|
+
"""List all registered resources."""
|
|
431
|
+
self._ensure_loaded()
|
|
432
|
+
return list(self._resources.values())
|
|
433
|
+
|
|
434
|
+
def list_schemas(self) -> List[Dict[str, Any]]:
|
|
435
|
+
"""List all resource schemas."""
|
|
436
|
+
return [res.to_mcp_schema() for res in self.list_all()]
|
|
437
|
+
|
|
438
|
+
def list_paginated(
|
|
439
|
+
self,
|
|
440
|
+
cursor: Optional[str] = None,
|
|
441
|
+
page_size: int = DEFAULT_PAGE_SIZE,
|
|
442
|
+
) -> Tuple[List[Dict[str, Any]], Optional[str]]:
|
|
443
|
+
"""
|
|
444
|
+
List resource schemas with pagination per MCP 2025-11-25 spec.
|
|
445
|
+
|
|
446
|
+
Args:
|
|
447
|
+
cursor: Opaque cursor from previous response
|
|
448
|
+
page_size: Number of items per page
|
|
449
|
+
|
|
450
|
+
Returns:
|
|
451
|
+
Tuple of (resources list, nextCursor or None)
|
|
452
|
+
"""
|
|
453
|
+
all_resources = self.list_all()
|
|
454
|
+
total = len(all_resources)
|
|
455
|
+
|
|
456
|
+
offset = 0
|
|
457
|
+
if cursor:
|
|
458
|
+
offset, _ = decode_cursor(cursor)
|
|
459
|
+
if offset < 0 or offset >= total:
|
|
460
|
+
raise ValueError(f"Invalid cursor: offset {offset} out of range")
|
|
461
|
+
|
|
462
|
+
page_size = min(page_size, MAX_PAGE_SIZE)
|
|
463
|
+
end = min(offset + page_size, total)
|
|
464
|
+
page = all_resources[offset:end]
|
|
465
|
+
schemas = [res.to_mcp_schema() for res in page]
|
|
466
|
+
|
|
467
|
+
next_cursor = None
|
|
468
|
+
if end < total:
|
|
469
|
+
next_cursor = encode_cursor(end)
|
|
470
|
+
|
|
471
|
+
return schemas, next_cursor
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
class MCPPromptRegistry:
|
|
475
|
+
"""Registry for MCP prompts."""
|
|
476
|
+
|
|
477
|
+
def __init__(self):
|
|
478
|
+
self._prompts: Dict[str, MCPPromptDefinition] = {}
|
|
479
|
+
self._lazy_loaders: List[Callable[[], None]] = []
|
|
480
|
+
|
|
481
|
+
def register(
|
|
482
|
+
self,
|
|
483
|
+
name: str,
|
|
484
|
+
handler: Callable,
|
|
485
|
+
description: Optional[str] = None,
|
|
486
|
+
arguments: Optional[List[Dict[str, Any]]] = None,
|
|
487
|
+
) -> None:
|
|
488
|
+
"""Register a prompt."""
|
|
489
|
+
if description is None:
|
|
490
|
+
description = inspect.getdoc(handler) or f"Prompt: {name}"
|
|
491
|
+
if "\n" in description:
|
|
492
|
+
description = description.split("\n")[0]
|
|
493
|
+
|
|
494
|
+
if arguments is None:
|
|
495
|
+
arguments = self._generate_arguments(handler)
|
|
496
|
+
|
|
497
|
+
self._prompts[name] = MCPPromptDefinition(
|
|
498
|
+
name=name,
|
|
499
|
+
description=description,
|
|
500
|
+
handler=handler,
|
|
501
|
+
arguments=arguments,
|
|
502
|
+
)
|
|
503
|
+
logger.debug(f"Registered MCP prompt: {name}")
|
|
504
|
+
|
|
505
|
+
def register_lazy(self, loader: Callable[[], None]) -> None:
|
|
506
|
+
"""Register a lazy loader."""
|
|
507
|
+
self._lazy_loaders.append(loader)
|
|
508
|
+
|
|
509
|
+
def _ensure_loaded(self) -> None:
|
|
510
|
+
"""Ensure all lazy loaders have been called."""
|
|
511
|
+
for loader in self._lazy_loaders:
|
|
512
|
+
try:
|
|
513
|
+
loader()
|
|
514
|
+
except Exception as e:
|
|
515
|
+
logger.warning(f"Lazy loader failed: {e}")
|
|
516
|
+
self._lazy_loaders.clear()
|
|
517
|
+
|
|
518
|
+
def get(self, name: str) -> Optional[MCPPromptDefinition]:
|
|
519
|
+
"""Get a prompt by name."""
|
|
520
|
+
self._ensure_loaded()
|
|
521
|
+
return self._prompts.get(name)
|
|
522
|
+
|
|
523
|
+
def list_all(self) -> List[MCPPromptDefinition]:
|
|
524
|
+
"""List all registered prompts."""
|
|
525
|
+
self._ensure_loaded()
|
|
526
|
+
return list(self._prompts.values())
|
|
527
|
+
|
|
528
|
+
def list_schemas(self) -> List[Dict[str, Any]]:
|
|
529
|
+
"""List all prompt schemas."""
|
|
530
|
+
return [prompt.to_mcp_schema() for prompt in self.list_all()]
|
|
531
|
+
|
|
532
|
+
def list_paginated(
|
|
533
|
+
self,
|
|
534
|
+
cursor: Optional[str] = None,
|
|
535
|
+
page_size: int = DEFAULT_PAGE_SIZE,
|
|
536
|
+
) -> Tuple[List[Dict[str, Any]], Optional[str]]:
|
|
537
|
+
"""
|
|
538
|
+
List prompt schemas with pagination per MCP 2025-11-25 spec.
|
|
539
|
+
|
|
540
|
+
Args:
|
|
541
|
+
cursor: Opaque cursor from previous response
|
|
542
|
+
page_size: Number of items per page
|
|
543
|
+
|
|
544
|
+
Returns:
|
|
545
|
+
Tuple of (prompts list, nextCursor or None)
|
|
546
|
+
"""
|
|
547
|
+
all_prompts = self.list_all()
|
|
548
|
+
total = len(all_prompts)
|
|
549
|
+
|
|
550
|
+
offset = 0
|
|
551
|
+
if cursor:
|
|
552
|
+
offset, _ = decode_cursor(cursor)
|
|
553
|
+
if offset < 0 or offset >= total:
|
|
554
|
+
raise ValueError(f"Invalid cursor: offset {offset} out of range")
|
|
555
|
+
|
|
556
|
+
page_size = min(page_size, MAX_PAGE_SIZE)
|
|
557
|
+
end = min(offset + page_size, total)
|
|
558
|
+
page = all_prompts[offset:end]
|
|
559
|
+
schemas = [prompt.to_mcp_schema() for prompt in page]
|
|
560
|
+
|
|
561
|
+
next_cursor = None
|
|
562
|
+
if end < total:
|
|
563
|
+
next_cursor = encode_cursor(end)
|
|
564
|
+
|
|
565
|
+
return schemas, next_cursor
|
|
566
|
+
|
|
567
|
+
def _generate_arguments(self, handler: Callable) -> List[Dict[str, Any]]:
|
|
568
|
+
"""Generate prompt arguments from function signature."""
|
|
569
|
+
sig = inspect.signature(handler)
|
|
570
|
+
arguments = []
|
|
571
|
+
|
|
572
|
+
for param_name, param in sig.parameters.items():
|
|
573
|
+
if param_name in ("self", "cls"):
|
|
574
|
+
continue
|
|
575
|
+
|
|
576
|
+
arg = {
|
|
577
|
+
"name": param_name,
|
|
578
|
+
"required": param.default is inspect.Parameter.empty,
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
# Add description if available
|
|
582
|
+
docstring = inspect.getdoc(handler)
|
|
583
|
+
if docstring and f":param {param_name}:" in docstring:
|
|
584
|
+
# Extract description
|
|
585
|
+
pass
|
|
586
|
+
|
|
587
|
+
arguments.append(arg)
|
|
588
|
+
|
|
589
|
+
return arguments
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
# Global registries
|
|
593
|
+
_tool_registry = MCPToolRegistry()
|
|
594
|
+
_resource_registry = MCPResourceRegistry()
|
|
595
|
+
_prompt_registry = MCPPromptRegistry()
|
|
596
|
+
|
|
597
|
+
|
|
598
|
+
def register_tool(
|
|
599
|
+
name: str,
|
|
600
|
+
handler: Optional[Callable] = None,
|
|
601
|
+
description: Optional[str] = None,
|
|
602
|
+
input_schema: Optional[Dict[str, Any]] = None,
|
|
603
|
+
output_schema: Optional[Dict[str, Any]] = None,
|
|
604
|
+
annotations: Optional[Dict[str, Any]] = None,
|
|
605
|
+
) -> Union[Callable, None]:
|
|
606
|
+
"""
|
|
607
|
+
Register a tool with the global registry.
|
|
608
|
+
|
|
609
|
+
Can be used as a decorator or called directly.
|
|
610
|
+
|
|
611
|
+
Example:
|
|
612
|
+
@register_tool("praisonai.memory.show")
|
|
613
|
+
def show_memory(session_id: str = None) -> str:
|
|
614
|
+
'''Show memory contents.'''
|
|
615
|
+
...
|
|
616
|
+
|
|
617
|
+
# Or directly:
|
|
618
|
+
register_tool("praisonai.memory.show", show_memory)
|
|
619
|
+
"""
|
|
620
|
+
def decorator(fn: Callable) -> Callable:
|
|
621
|
+
_tool_registry.register(
|
|
622
|
+
name=name,
|
|
623
|
+
handler=fn,
|
|
624
|
+
description=description,
|
|
625
|
+
input_schema=input_schema,
|
|
626
|
+
output_schema=output_schema,
|
|
627
|
+
annotations=annotations,
|
|
628
|
+
)
|
|
629
|
+
return fn
|
|
630
|
+
|
|
631
|
+
if handler is not None:
|
|
632
|
+
decorator(handler)
|
|
633
|
+
return None
|
|
634
|
+
return decorator
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+
def register_resource(
|
|
638
|
+
uri: str,
|
|
639
|
+
handler: Optional[Callable] = None,
|
|
640
|
+
name: Optional[str] = None,
|
|
641
|
+
description: Optional[str] = None,
|
|
642
|
+
mime_type: str = "application/json",
|
|
643
|
+
) -> Union[Callable, None]:
|
|
644
|
+
"""
|
|
645
|
+
Register a resource with the global registry.
|
|
646
|
+
|
|
647
|
+
Can be used as a decorator or called directly.
|
|
648
|
+
"""
|
|
649
|
+
def decorator(fn: Callable) -> Callable:
|
|
650
|
+
_resource_registry.register(
|
|
651
|
+
uri=uri,
|
|
652
|
+
handler=fn,
|
|
653
|
+
name=name,
|
|
654
|
+
description=description,
|
|
655
|
+
mime_type=mime_type,
|
|
656
|
+
)
|
|
657
|
+
return fn
|
|
658
|
+
|
|
659
|
+
if handler is not None:
|
|
660
|
+
decorator(handler)
|
|
661
|
+
return None
|
|
662
|
+
return decorator
|
|
663
|
+
|
|
664
|
+
|
|
665
|
+
def register_prompt(
|
|
666
|
+
name: str,
|
|
667
|
+
handler: Optional[Callable] = None,
|
|
668
|
+
description: Optional[str] = None,
|
|
669
|
+
arguments: Optional[List[Dict[str, Any]]] = None,
|
|
670
|
+
) -> Union[Callable, None]:
|
|
671
|
+
"""
|
|
672
|
+
Register a prompt with the global registry.
|
|
673
|
+
|
|
674
|
+
Can be used as a decorator or called directly.
|
|
675
|
+
"""
|
|
676
|
+
def decorator(fn: Callable) -> Callable:
|
|
677
|
+
_prompt_registry.register(
|
|
678
|
+
name=name,
|
|
679
|
+
handler=fn,
|
|
680
|
+
description=description,
|
|
681
|
+
arguments=arguments,
|
|
682
|
+
)
|
|
683
|
+
return fn
|
|
684
|
+
|
|
685
|
+
if handler is not None:
|
|
686
|
+
decorator(handler)
|
|
687
|
+
return None
|
|
688
|
+
return decorator
|
|
689
|
+
|
|
690
|
+
|
|
691
|
+
def get_tool_registry() -> MCPToolRegistry:
|
|
692
|
+
"""Get the global tool registry."""
|
|
693
|
+
return _tool_registry
|
|
694
|
+
|
|
695
|
+
|
|
696
|
+
def get_resource_registry() -> MCPResourceRegistry:
|
|
697
|
+
"""Get the global resource registry."""
|
|
698
|
+
return _resource_registry
|
|
699
|
+
|
|
700
|
+
|
|
701
|
+
def get_prompt_registry() -> MCPPromptRegistry:
|
|
702
|
+
"""Get the global prompt registry."""
|
|
703
|
+
return _prompt_registry
|