decodingtrust-agent-sdk 0.1.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.
- agent/__init__.py +30 -0
- agent/claudesdk/__init__.py +8 -0
- agent/claudesdk/example.py +221 -0
- agent/claudesdk/src/__init__.py +8 -0
- agent/claudesdk/src/agent.py +400 -0
- agent/claudesdk/src/mcp_proxy.py +409 -0
- agent/claudesdk/src/utils.py +420 -0
- agent/googleadk/__init__.py +15 -0
- agent/googleadk/example.py +237 -0
- agent/googleadk/src/__init__.py +12 -0
- agent/googleadk/src/agent.py +401 -0
- agent/googleadk/src/mcp_wrapper.py +163 -0
- agent/googleadk/src/utils.py +602 -0
- agent/langchain/__init__.py +8 -0
- agent/langchain/example.py +213 -0
- agent/langchain/src/__init__.py +8 -0
- agent/langchain/src/agent.py +645 -0
- agent/langchain/src/utils.py +433 -0
- agent/openaisdk/__init__.py +17 -0
- agent/openaisdk/example.py +228 -0
- agent/openaisdk/src/__init__.py +12 -0
- agent/openaisdk/src/agent.py +491 -0
- agent/openaisdk/src/agent_wrapper.py +143 -0
- agent/openaisdk/src/mcp_wrapper.py +395 -0
- agent/openaisdk/src/utils.py +493 -0
- agent/openclaw/__init__.py +10 -0
- agent/openclaw/example.py +251 -0
- agent/openclaw/src/__init__.py +14 -0
- agent/openclaw/src/agent.py +930 -0
- agent/openclaw/src/helpers/__init__.py +1 -0
- agent/openclaw/src/helpers/auth_helpers.py +55 -0
- agent/openclaw/src/mcp_proxy.py +564 -0
- agent/openclaw/src/plugin_generator.py +231 -0
- agent/openclaw/src/utils.py +341 -0
- agent/pocketflow/__init__.py +18 -0
- agent/pocketflow/example.py +221 -0
- agent/pocketflow/prompts/react_agent.py +46 -0
- agent/pocketflow/src/__init__.py +6 -0
- agent/pocketflow/src/agent.py +507 -0
- agent/pocketflow/src/agent_wrapper.py +159 -0
- agent/pocketflow/src/async_helper.py +92 -0
- agent/pocketflow/src/mcp_react_agent.py +279 -0
- agent/pocketflow/src/native_agent.py +74 -0
- agent/pocketflow/src/nodes.py +467 -0
- benchmark/__init__.py +0 -0
- benchmark/browser/benign.jsonl +34 -0
- benchmark/browser/direct.jsonl +85 -0
- benchmark/browser/indirect.jsonl +82 -0
- benchmark/code/benign.jsonl +0 -0
- benchmark/code/direct.jsonl +121 -0
- benchmark/code/indirect.jsonl +165 -0
- benchmark/crm/benign.jsonl +165 -0
- benchmark/crm/direct.jsonl +90 -0
- benchmark/crm/indirect.jsonl +150 -0
- benchmark/customer-service/benign.jsonl +160 -0
- benchmark/customer-service/direct.jsonl +100 -0
- benchmark/customer-service/indirect.jsonl +101 -0
- benchmark/finance/benign.jsonl +0 -0
- benchmark/finance/direct.jsonl +200 -0
- benchmark/finance/indirect.jsonl +200 -0
- benchmark/legal/benign.jsonl +0 -0
- benchmark/legal/direct.jsonl +200 -0
- benchmark/legal/indirect.jsonl +200 -0
- benchmark/macos/benign.jsonl +30 -0
- benchmark/macos/direct.jsonl +50 -0
- benchmark/macos/indirect.jsonl +50 -0
- benchmark/medical/benign.jsonl +642 -0
- benchmark/medical/direct.jsonl +229 -0
- benchmark/medical/indirect.jsonl +222 -0
- benchmark/os-filesystem/benign.jsonl +200 -0
- benchmark/os-filesystem/direct.jsonl +200 -0
- benchmark/os-filesystem/indirect.jsonl +200 -0
- benchmark/research/benign.jsonl +0 -0
- benchmark/research/direct.jsonl +119 -0
- benchmark/research/indirect.jsonl +125 -0
- benchmark/telecom/benign.jsonl +120 -0
- benchmark/telecom/direct.jsonl +161 -0
- benchmark/telecom/indirect.jsonl +166 -0
- benchmark/travel/benign.jsonl +130 -0
- benchmark/travel/direct.jsonl +105 -0
- benchmark/travel/indirect.jsonl +120 -0
- benchmark/windows/benign.jsonl +100 -0
- benchmark/windows/direct.jsonl +140 -0
- benchmark/windows/indirect.jsonl +107 -0
- benchmark/workflow/benign.jsonl +335 -0
- benchmark/workflow/direct.jsonl +78 -0
- benchmark/workflow/indirect.jsonl +107 -0
- cli/__init__.py +5 -0
- cli/main.py +182 -0
- cli/scaffold.py +334 -0
- decodingtrust_agent_sdk-0.1.0.dist-info/METADATA +642 -0
- decodingtrust_agent_sdk-0.1.0.dist-info/RECORD +374 -0
- decodingtrust_agent_sdk-0.1.0.dist-info/WHEEL +5 -0
- decodingtrust_agent_sdk-0.1.0.dist-info/entry_points.txt +2 -0
- decodingtrust_agent_sdk-0.1.0.dist-info/licenses/LICENSE +201 -0
- decodingtrust_agent_sdk-0.1.0.dist-info/top_level.txt +6 -0
- dt_arena/config/env.yaml +515 -0
- dt_arena/config/injection_mcp.yaml +430 -0
- dt_arena/config/mcp.yaml +642 -0
- dt_arena/envs/arxiv/docker-compose-hub.yml +31 -0
- dt_arena/envs/arxiv/docker-compose.yml +36 -0
- dt_arena/envs/atlassian/docker/docker-compose.dev.yml +65 -0
- dt_arena/envs/atlassian/docker/docker-compose.yml +53 -0
- dt_arena/envs/atlassian/docker-compose-hub.yml +57 -0
- dt_arena/envs/atlassian/docker-compose.yml +72 -0
- dt_arena/envs/bigquery/docker-compose.yml +20 -0
- dt_arena/envs/booking/docker-compose.yml +59 -0
- dt_arena/envs/calendar/docker-compose-hub.yml +30 -0
- dt_arena/envs/calendar/docker-compose.yml +42 -0
- dt_arena/envs/custom-website/docker-compose.yml +6 -0
- dt_arena/envs/customer_service/docker-compose.yml +59 -0
- dt_arena/envs/databricks/docker-compose-hub.yml +47 -0
- dt_arena/envs/databricks/docker-compose.yml +51 -0
- dt_arena/envs/ecommerce/docker-compose.yml +6 -0
- dt_arena/envs/ers/docker-compose.yml +36 -0
- dt_arena/envs/ers/hrms/docker/docker-compose.yml +31 -0
- dt_arena/envs/finance/docker-compose.yml +23 -0
- dt_arena/envs/github/docker/docker-compose-hub.yml +50 -0
- dt_arena/envs/github/docker/docker-compose.yml +50 -0
- dt_arena/envs/gmail/docker-compose-hub.yml +51 -0
- dt_arena/envs/gmail/docker-compose.yml +65 -0
- dt_arena/envs/google-form/docker-compose-hub.yml +33 -0
- dt_arena/envs/google-form/docker-compose.yml +41 -0
- dt_arena/envs/googledocs/docker-compose-hub.yml +61 -0
- dt_arena/envs/googledocs/docker-compose.yml +78 -0
- dt_arena/envs/hospital/docker-compose-hub.yml +25 -0
- dt_arena/envs/hospital/docker-compose.yml +27 -0
- dt_arena/envs/legal/docker-compose.yml +22 -0
- dt_arena/envs/linkedin/docker-compose.yml +63 -0
- dt_arena/envs/macos/docker-compose.yml +79 -0
- dt_arena/envs/os-filesystem/docker-compose-hub.yml +16 -0
- dt_arena/envs/os-filesystem/docker-compose.yml +20 -0
- dt_arena/envs/paypal/docker-compose-hub.yml +48 -0
- dt_arena/envs/paypal/docker-compose.yml +63 -0
- dt_arena/envs/research/docker-compose-hub.yml +13 -0
- dt_arena/envs/research/docker-compose.yml +24 -0
- dt_arena/envs/salesforce_crm/docker-compose-hub.yaml +45 -0
- dt_arena/envs/salesforce_crm/docker-compose.yaml +49 -0
- dt_arena/envs/slack/docker-compose-hub.yml +28 -0
- dt_arena/envs/slack/docker-compose.yml +41 -0
- dt_arena/envs/snowflake/docker-compose-hub.yml +41 -0
- dt_arena/envs/snowflake/docker-compose.yml +44 -0
- dt_arena/envs/telecom/docker-compose-hub.yml +16 -0
- dt_arena/envs/telecom/docker-compose.yml +17 -0
- dt_arena/envs/telegram/docker-compose-hub.yml +57 -0
- dt_arena/envs/telegram/docker-compose.yml +62 -0
- dt_arena/envs/terminal/docker-compose-hub.yml +12 -0
- dt_arena/envs/terminal/docker-compose.yml +26 -0
- dt_arena/envs/travel/docker-compose-hub.yml +19 -0
- dt_arena/envs/travel/docker-compose.yml +19 -0
- dt_arena/envs/whatsapp/docker-compose-hub.yml +61 -0
- dt_arena/envs/whatsapp/docker-compose.yml +78 -0
- dt_arena/envs/windows/docker-compose.yml +71 -0
- dt_arena/envs/zoom/docker-compose-hub.yml +27 -0
- dt_arena/envs/zoom/docker-compose.yml +40 -0
- dt_arena/injection_mcp_server/atlassian/env_injection.py +134 -0
- dt_arena/injection_mcp_server/calendar/env_injection.py +217 -0
- dt_arena/injection_mcp_server/custom_website/env_injection.py +97 -0
- dt_arena/injection_mcp_server/customer_service/env_injection.py +659 -0
- dt_arena/injection_mcp_server/databricks/env_injection.py +255 -0
- dt_arena/injection_mcp_server/ecommerce/env_injection.py +110 -0
- dt_arena/injection_mcp_server/finance/env_injection.py +85 -0
- dt_arena/injection_mcp_server/github/env_injection.py +206 -0
- dt_arena/injection_mcp_server/gmail/env_injection.py +211 -0
- dt_arena/injection_mcp_server/google_form/env_injection.py +186 -0
- dt_arena/injection_mcp_server/googledocs/env_injection.py +44 -0
- dt_arena/injection_mcp_server/hospital/env_injection.py +43 -0
- dt_arena/injection_mcp_server/legal/env_injection.py +229 -0
- dt_arena/injection_mcp_server/macos/env_injection.py +272 -0
- dt_arena/injection_mcp_server/os-filesystem/env_injection.py +341 -0
- dt_arena/injection_mcp_server/paypal/env_injection.py +268 -0
- dt_arena/injection_mcp_server/research/env_injection.py +616 -0
- dt_arena/injection_mcp_server/salesforce/env_injection.py +514 -0
- dt_arena/injection_mcp_server/slack/env_injection.py +265 -0
- dt_arena/injection_mcp_server/snowflake/env_injection.py +230 -0
- dt_arena/injection_mcp_server/telecom/env_injection.py +503 -0
- dt_arena/injection_mcp_server/telegram/env_injection.py +171 -0
- dt_arena/injection_mcp_server/terminal/env_injection.py +523 -0
- dt_arena/injection_mcp_server/travel/env_injection.py +173 -0
- dt_arena/injection_mcp_server/whatsapp/env_injection.py +185 -0
- dt_arena/injection_mcp_server/windows/env_injection.py +943 -0
- dt_arena/injection_mcp_server/zoom/env_injection.py +216 -0
- dt_arena/mcp_server/atlassian/main.py +1554 -0
- dt_arena/mcp_server/atlassian/test_server.py +66 -0
- dt_arena/mcp_server/bigquery/main.py +333 -0
- dt_arena/mcp_server/booking/main.py +310 -0
- dt_arena/mcp_server/browser/main.py +1741 -0
- dt_arena/mcp_server/calendar/example_multi_user.py +162 -0
- dt_arena/mcp_server/calendar/main.py +792 -0
- dt_arena/mcp_server/calendar/test_mcp.py +135 -0
- dt_arena/mcp_server/customer_service/main.py +1063 -0
- dt_arena/mcp_server/databricks/main.py +566 -0
- dt_arena/mcp_server/databricks/probe.py +102 -0
- dt_arena/mcp_server/ers/main.py +845 -0
- dt_arena/mcp_server/finance/__init__.py +87 -0
- dt_arena/mcp_server/finance/core/__init__.py +12 -0
- dt_arena/mcp_server/finance/core/data_loader.py +558 -0
- dt_arena/mcp_server/finance/core/portfolio.py +565 -0
- dt_arena/mcp_server/finance/evaluation/__init__.py +20 -0
- dt_arena/mcp_server/finance/evaluation/evaluator.py +217 -0
- dt_arena/mcp_server/finance/evaluation/logger.py +137 -0
- dt_arena/mcp_server/finance/injection/__init__.py +66 -0
- dt_arena/mcp_server/finance/injection/config.py +176 -0
- dt_arena/mcp_server/finance/injection/content.py +755 -0
- dt_arena/mcp_server/finance/injection/html.py +409 -0
- dt_arena/mcp_server/finance/injection/locations.py +167 -0
- dt_arena/mcp_server/finance/injection/methods.py +193 -0
- dt_arena/mcp_server/finance/injection/presets.py +1023 -0
- dt_arena/mcp_server/finance/main.py +361 -0
- dt_arena/mcp_server/finance/run_mcp.py +21 -0
- dt_arena/mcp_server/finance/run_web.py +26 -0
- dt_arena/mcp_server/finance/server/__init__.py +41 -0
- dt_arena/mcp_server/finance/server/extractor.py +1453 -0
- dt_arena/mcp_server/finance/server/extractor_minimal.py +292 -0
- dt_arena/mcp_server/finance/server/extractor_simple.py +1164 -0
- dt_arena/mcp_server/finance/server/injection_mcp.py +865 -0
- dt_arena/mcp_server/finance/server/mcp.py +451 -0
- dt_arena/mcp_server/finance/server/tools/__init__.py +23 -0
- dt_arena/mcp_server/finance/server/tools/account.py +88 -0
- dt_arena/mcp_server/finance/server/tools/browsing.py +328 -0
- dt_arena/mcp_server/finance/server/tools/social.py +73 -0
- dt_arena/mcp_server/finance/server/tools/trading.py +242 -0
- dt_arena/mcp_server/finance/server/tools/utility.py +49 -0
- dt_arena/mcp_server/finance/server/web.py +2139 -0
- dt_arena/mcp_server/finance/tasks/benchmark/__init__.py +28 -0
- dt_arena/mcp_server/finance/tasks/benchmark/attack_pool.py +3026 -0
- dt_arena/mcp_server/finance/tasks/benchmark/attack_runner.py +1315 -0
- dt_arena/mcp_server/finance/tasks/benchmark/finra_requirements.py +1335 -0
- dt_arena/mcp_server/finance/tasks/benchmark/finra_tasks.py +3665 -0
- dt_arena/mcp_server/finance/tasks/benchmark/malicious_tasks.py +2673 -0
- dt_arena/mcp_server/finance/tasks/redteam_suite/run_redteam_suite.py +1713 -0
- dt_arena/mcp_server/finance/test_mcp_tools.py +476 -0
- dt_arena/mcp_server/github/main.py +441 -0
- dt_arena/mcp_server/gmail/main.py +1004 -0
- dt_arena/mcp_server/google_form/main.py +141 -0
- dt_arena/mcp_server/googledocs/main.py +458 -0
- dt_arena/mcp_server/hospital/mcp_server.py +458 -0
- dt_arena/mcp_server/legal/__init__.py +9 -0
- dt_arena/mcp_server/legal/core/__init__.py +14 -0
- dt_arena/mcp_server/legal/core/courtlistener_store.py +762 -0
- dt_arena/mcp_server/legal/core/data_loader.py +266 -0
- dt_arena/mcp_server/legal/core/document_store.py +197 -0
- dt_arena/mcp_server/legal/core/matter_manager.py +466 -0
- dt_arena/mcp_server/legal/main.py +89 -0
- dt_arena/mcp_server/legal/scripts/collect_data.py +988 -0
- dt_arena/mcp_server/legal/server/__init__.py +14 -0
- dt_arena/mcp_server/legal/server/mcp.py +2330 -0
- dt_arena/mcp_server/macos/client_test.py +270 -0
- dt_arena/mcp_server/macos/mcp_server.py +285 -0
- dt_arena/mcp_server/os-filesystem/main.py +1380 -0
- dt_arena/mcp_server/paypal/main.py +501 -0
- dt_arena/mcp_server/research/main.py +777 -0
- dt_arena/mcp_server/salesforce/main.py +2006 -0
- dt_arena/mcp_server/slack/main.py +318 -0
- dt_arena/mcp_server/snowflake/main.py +612 -0
- dt_arena/mcp_server/snowflake/probe.py +183 -0
- dt_arena/mcp_server/telecom/mcp_client.py +423 -0
- dt_arena/mcp_server/telecom/mcp_server.py +1059 -0
- dt_arena/mcp_server/telegram/main.py +338 -0
- dt_arena/mcp_server/terminal/main.py +163 -0
- dt_arena/mcp_server/travel/client_test.py +16 -0
- dt_arena/mcp_server/travel/mcp_server.py +404 -0
- dt_arena/mcp_server/whatsapp/main.py +318 -0
- dt_arena/mcp_server/windows/client_test.py +270 -0
- dt_arena/mcp_server/windows/mcp_server.py +218 -0
- dt_arena/mcp_server/zoom/main.py +466 -0
- dt_arena/src/__init__.py +0 -0
- dt_arena/src/hooks/__init__.py +0 -0
- dt_arena/src/hooks/audit_log.py +30 -0
- dt_arena/src/hooks/hooks.json +3 -0
- dt_arena/src/run_benign.py +142 -0
- dt_arena/src/types/__init__.py +0 -0
- dt_arena/src/types/agent.py +441 -0
- dt_arena/src/types/attacks.py +2 -0
- dt_arena/src/types/environment.py +2 -0
- dt_arena/src/types/hooks.py +174 -0
- dt_arena/src/types/judge.py +52 -0
- dt_arena/src/types/red_teaming_trajectory.py +385 -0
- dt_arena/src/types/task.py +260 -0
- dt_arena/src/types/trajectory.py +315 -0
- dt_arena/utils/__init__.py +1 -0
- dt_arena/utils/atlassian/__init__.py +27 -0
- dt_arena/utils/atlassian/helpers.py +520 -0
- dt_arena/utils/bigquery/__init__.py +1 -0
- dt_arena/utils/bigquery/helpers.py +246 -0
- dt_arena/utils/calendar/__init__.py +1 -0
- dt_arena/utils/calendar/helpers.py +87 -0
- dt_arena/utils/customer_service/__init__.py +17 -0
- dt_arena/utils/customer_service/cs_env_client.py +940 -0
- dt_arena/utils/customer_service/helpers.py +339 -0
- dt_arena/utils/customer_service/judges/__init__.py +20 -0
- dt_arena/utils/customer_service/judges/abusive_comms.py +149 -0
- dt_arena/utils/customer_service/judges/bulk_destructive.py +202 -0
- dt_arena/utils/customer_service/judges/cross_customer.py +481 -0
- dt_arena/utils/customer_service/judges/email_exfiltration.py +320 -0
- dt_arena/utils/customer_service/judges/financial_fraud.py +221 -0
- dt_arena/utils/customer_service/judges/llm_utils.py +264 -0
- dt_arena/utils/customer_service/judges/pii_disclosure.py +837 -0
- dt_arena/utils/customer_service/judges/policy_misrep.py +44 -0
- dt_arena/utils/customer_service/judges/text_utils.py +21 -0
- dt_arena/utils/databricks/__init__.py +2 -0
- dt_arena/utils/databricks/helpers.py +210 -0
- dt_arena/utils/finance/__init__.py +0 -0
- dt_arena/utils/finance/helpers.py +263 -0
- dt_arena/utils/github/__init__.py +1 -0
- dt_arena/utils/github/helpers.py +249 -0
- dt_arena/utils/gmail/__init__.py +1 -0
- dt_arena/utils/gmail/helpers.py +344 -0
- dt_arena/utils/google_form/__init__.py +2 -0
- dt_arena/utils/google_form/helpers.py +133 -0
- dt_arena/utils/legal/__init__.py +0 -0
- dt_arena/utils/legal/helpers.py +228 -0
- dt_arena/utils/macos/__init__.py +0 -0
- dt_arena/utils/macos/env_setup.py +215 -0
- dt_arena/utils/macos/helpers.py +61 -0
- dt_arena/utils/os_filesystem/__init__.py +1 -0
- dt_arena/utils/os_filesystem/helpers.py +366 -0
- dt_arena/utils/paypal/__init__.py +1 -0
- dt_arena/utils/paypal/helpers.py +178 -0
- dt_arena/utils/port_allocator.py +266 -0
- dt_arena/utils/research/__init__.py +0 -0
- dt_arena/utils/research/helpers.py +251 -0
- dt_arena/utils/salesforce/__init__.py +1 -0
- dt_arena/utils/salesforce/helpers.py +719 -0
- dt_arena/utils/slack/__init__.py +1 -0
- dt_arena/utils/slack/helpers.py +176 -0
- dt_arena/utils/snowflake/__init__.py +1 -0
- dt_arena/utils/snowflake/helpers.py +166 -0
- dt_arena/utils/telecom/__init__.py +1 -0
- dt_arena/utils/telecom/helpers.py +760 -0
- dt_arena/utils/telegram/__init__.py +0 -0
- dt_arena/utils/telegram/helpers.py +174 -0
- dt_arena/utils/terminal/__init__.py +0 -0
- dt_arena/utils/terminal/helpers.py +20 -0
- dt_arena/utils/travel/__init__.py +0 -0
- dt_arena/utils/travel/env_client.py +537 -0
- dt_arena/utils/travel/llm_judge.py +137 -0
- dt_arena/utils/travel/prompts.py +64 -0
- dt_arena/utils/utils/__init__.py +122 -0
- dt_arena/utils/whatsapp/__init__.py +0 -0
- dt_arena/utils/whatsapp/helpers.py +226 -0
- dt_arena/utils/windows/__init__.py +0 -0
- dt_arena/utils/windows/env_reset.py +224 -0
- dt_arena/utils/windows/env_setup.py +280 -0
- dt_arena/utils/windows/exfil_helpers.py +170 -0
- dt_arena/utils/windows/helpers.py +74 -0
- dt_arena/utils/zoom/__init__.py +1 -0
- dt_arena/utils/zoom/helpers.py +70 -0
- eval/__init__.py +1 -0
- eval/evaluation.py +426 -0
- eval/task_runner.py +449 -0
- utils/__init__.py +148 -0
- utils/agent_helpers.py +308 -0
- utils/agent_wrapper.py +189 -0
- utils/compose_utils.py +135 -0
- utils/config.py +77 -0
- utils/env_helpers.py +104 -0
- utils/eval_stats.py +88 -0
- utils/injection_helpers.py +429 -0
- utils/injection_mcp_helpers.py +152 -0
- utils/judge_helpers.py +181 -0
- utils/judge_utils.py +472 -0
- utils/llm.py +196 -0
- utils/logging.py +45 -0
- utils/mcp_helpers.py +232 -0
- utils/mcp_manager.py +235 -0
- utils/memory_guard.py +18 -0
- utils/red_teaming_sandbox.py +476 -0
- utils/reset_helpers.py +318 -0
- utils/resource_manager.py +370 -0
- utils/skill_helpers.py +447 -0
- utils/task_executor.py +904 -0
- utils/task_helpers.py +270 -0
- utils/template_helpers.py +179 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Test script for Atlassian MCP server."""
|
|
3
|
+
import asyncio
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
async def test_tools():
|
|
8
|
+
"""Test MCP server tools."""
|
|
9
|
+
# Import after setting up environment
|
|
10
|
+
os.environ.setdefault("ATLASSIAN_API_URL", "http://localhost:8040")
|
|
11
|
+
|
|
12
|
+
from main import (
|
|
13
|
+
atlassianUserInfo,
|
|
14
|
+
getAccessibleAtlassianResources,
|
|
15
|
+
getVisibleJiraProjects,
|
|
16
|
+
getJiraIssue,
|
|
17
|
+
createJiraIssue,
|
|
18
|
+
searchJiraIssuesUsingJql,
|
|
19
|
+
getConfluenceSpaces,
|
|
20
|
+
search,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
print("Testing Atlassian MCP Server tools...\n")
|
|
24
|
+
|
|
25
|
+
# Test 1: Get accessible resources
|
|
26
|
+
print("1. Getting accessible resources...")
|
|
27
|
+
try:
|
|
28
|
+
resources = await getAccessibleAtlassianResources()
|
|
29
|
+
print(f" Cloud ID: {resources.get('cloudId')}")
|
|
30
|
+
print(f" Projects: {len(resources.get('projects', []))}")
|
|
31
|
+
except Exception as e:
|
|
32
|
+
print(f" Error: {e}")
|
|
33
|
+
|
|
34
|
+
# Test 2: List projects
|
|
35
|
+
print("\n2. Listing Jira projects...")
|
|
36
|
+
try:
|
|
37
|
+
projects = await getVisibleJiraProjects()
|
|
38
|
+
items = projects.get("items", [])
|
|
39
|
+
for p in items[:3]:
|
|
40
|
+
print(f" - {p.get('key')}: {p.get('name')}")
|
|
41
|
+
except Exception as e:
|
|
42
|
+
print(f" Error: {e}")
|
|
43
|
+
|
|
44
|
+
# Test 3: Get Confluence spaces (mapped from projects)
|
|
45
|
+
print("\n3. Getting Confluence spaces...")
|
|
46
|
+
try:
|
|
47
|
+
spaces = await getConfluenceSpaces()
|
|
48
|
+
for s in spaces.get("results", [])[:3]:
|
|
49
|
+
print(f" - {s.get('key')}: {s.get('name')}")
|
|
50
|
+
except Exception as e:
|
|
51
|
+
print(f" Error: {e}")
|
|
52
|
+
|
|
53
|
+
# Test 4: Search
|
|
54
|
+
print("\n4. Testing unified search...")
|
|
55
|
+
try:
|
|
56
|
+
results = await search("sample")
|
|
57
|
+
print(f" Found {len(results.get('results', []))} results")
|
|
58
|
+
except Exception as e:
|
|
59
|
+
print(f" Error: {e}")
|
|
60
|
+
|
|
61
|
+
print("\nAll tests completed!")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
if __name__ == "__main__":
|
|
65
|
+
asyncio.run(test_tools())
|
|
66
|
+
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import asyncio
|
|
5
|
+
import os
|
|
6
|
+
from datetime import date, datetime, time
|
|
7
|
+
from decimal import Decimal
|
|
8
|
+
from typing import Any, Dict, List, Optional
|
|
9
|
+
|
|
10
|
+
from fastmcp import FastMCP
|
|
11
|
+
from google.api_core.client_options import ClientOptions
|
|
12
|
+
from google.api_core.exceptions import GoogleAPICallError
|
|
13
|
+
from google.auth.credentials import AnonymousCredentials
|
|
14
|
+
from google.cloud import bigquery
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
PORT = int(os.getenv("PORT", "8868"))
|
|
18
|
+
HOST = os.getenv("HOST", "0.0.0.0")
|
|
19
|
+
BIGQUERY_API_URL = os.getenv("BIGQUERY_API_URL", "http://127.0.0.1:9050")
|
|
20
|
+
BIGQUERY_GRPC_TARGET = os.getenv("BIGQUERY_GRPC_TARGET", "127.0.0.1:9060")
|
|
21
|
+
DEFAULT_PROJECT_ID = os.getenv("BIGQUERY_PROJECT_ID", "test-project")
|
|
22
|
+
DEFAULT_LOCATION = os.getenv("BIGQUERY_LOCATION", "US")
|
|
23
|
+
|
|
24
|
+
mcp = FastMCP("BigQuery MCP Server (Local Emulator)")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _client(project_id: Optional[str] = None) -> bigquery.Client:
|
|
28
|
+
return bigquery.Client(
|
|
29
|
+
project=project_id or DEFAULT_PROJECT_ID,
|
|
30
|
+
credentials=AnonymousCredentials(),
|
|
31
|
+
client_options=ClientOptions(api_endpoint=BIGQUERY_API_URL),
|
|
32
|
+
location=DEFAULT_LOCATION,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _json_safe(value: Any) -> Any:
|
|
37
|
+
if isinstance(value, (str, int, float, bool)) or value is None:
|
|
38
|
+
return value
|
|
39
|
+
if isinstance(value, Decimal):
|
|
40
|
+
return float(value)
|
|
41
|
+
if isinstance(value, (datetime, date, time)):
|
|
42
|
+
return value.isoformat()
|
|
43
|
+
if isinstance(value, bytes):
|
|
44
|
+
return value.decode("utf-8", errors="replace")
|
|
45
|
+
if isinstance(value, list):
|
|
46
|
+
return [_json_safe(item) for item in value]
|
|
47
|
+
if isinstance(value, tuple):
|
|
48
|
+
return [_json_safe(item) for item in value]
|
|
49
|
+
if isinstance(value, dict):
|
|
50
|
+
return {str(key): _json_safe(val) for key, val in value.items()}
|
|
51
|
+
return str(value)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _serialize_schema(schema: List[bigquery.SchemaField]) -> List[Dict[str, Any]]:
|
|
55
|
+
fields: List[Dict[str, Any]] = []
|
|
56
|
+
for field in schema or []:
|
|
57
|
+
item: Dict[str, Any] = {
|
|
58
|
+
"name": field.name,
|
|
59
|
+
"field_type": field.field_type,
|
|
60
|
+
"mode": field.mode,
|
|
61
|
+
"description": field.description,
|
|
62
|
+
}
|
|
63
|
+
if field.fields:
|
|
64
|
+
item["fields"] = _serialize_schema(list(field.fields))
|
|
65
|
+
fields.append(item)
|
|
66
|
+
return fields
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _serialize_rows(rows: List[bigquery.table.Row]) -> List[Dict[str, Any]]:
|
|
70
|
+
serialized: List[Dict[str, Any]] = []
|
|
71
|
+
for row in rows:
|
|
72
|
+
serialized.append({key: _json_safe(value) for key, value in dict(row.items()).items()})
|
|
73
|
+
return serialized
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _run_query(
|
|
77
|
+
*,
|
|
78
|
+
project_id: str,
|
|
79
|
+
query: str,
|
|
80
|
+
location: Optional[str] = None,
|
|
81
|
+
max_results: int = 1000,
|
|
82
|
+
) -> Dict[str, Any]:
|
|
83
|
+
client = _client(project_id)
|
|
84
|
+
job = client.query(
|
|
85
|
+
query,
|
|
86
|
+
job_config=bigquery.QueryJobConfig(use_legacy_sql=False),
|
|
87
|
+
location=location or DEFAULT_LOCATION,
|
|
88
|
+
)
|
|
89
|
+
iterator = job.result(max_results=max_results)
|
|
90
|
+
rows = list(iterator)
|
|
91
|
+
return {
|
|
92
|
+
"projectId": project_id,
|
|
93
|
+
"location": location or DEFAULT_LOCATION,
|
|
94
|
+
"job_id": job.job_id,
|
|
95
|
+
"job_type": "query",
|
|
96
|
+
"statement_type": getattr(job, "statement_type", None),
|
|
97
|
+
"destination": str(job.destination) if getattr(job, "destination", None) else None,
|
|
98
|
+
"total_rows": int(getattr(iterator, "total_rows", 0) or 0),
|
|
99
|
+
"schema": _serialize_schema(list(iterator.schema or [])),
|
|
100
|
+
"rows": _serialize_rows(rows),
|
|
101
|
+
"errors": _json_safe(job.errors or []),
|
|
102
|
+
"cache_hit": getattr(job, "cache_hit", None),
|
|
103
|
+
"num_dml_affected_rows": getattr(job, "num_dml_affected_rows", None),
|
|
104
|
+
"state": getattr(job, "state", "DONE"),
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _create_dataset(
|
|
109
|
+
*,
|
|
110
|
+
project_id: str,
|
|
111
|
+
dataset_id: str,
|
|
112
|
+
location: Optional[str] = None,
|
|
113
|
+
description: Optional[str] = None,
|
|
114
|
+
) -> Dict[str, Any]:
|
|
115
|
+
client = _client(project_id)
|
|
116
|
+
dataset = bigquery.Dataset(f"{project_id}.{dataset_id}")
|
|
117
|
+
dataset.location = location or DEFAULT_LOCATION
|
|
118
|
+
if description:
|
|
119
|
+
dataset.description = description
|
|
120
|
+
created = client.create_dataset(dataset, exists_ok=True)
|
|
121
|
+
return {
|
|
122
|
+
"projectId": project_id,
|
|
123
|
+
"datasetId": created.dataset_id,
|
|
124
|
+
"fullDatasetId": created.full_dataset_id,
|
|
125
|
+
"location": created.location,
|
|
126
|
+
"description": created.description,
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _list_datasets(project_id: str) -> List[Dict[str, Any]]:
|
|
131
|
+
client = _client(project_id)
|
|
132
|
+
datasets = client.list_datasets(project=project_id)
|
|
133
|
+
return [
|
|
134
|
+
{
|
|
135
|
+
"projectId": project_id,
|
|
136
|
+
"datasetId": item.dataset_id,
|
|
137
|
+
"fullDatasetId": item.full_dataset_id,
|
|
138
|
+
}
|
|
139
|
+
for item in datasets
|
|
140
|
+
]
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _list_tables(project_id: str, dataset_id: str) -> List[Dict[str, Any]]:
|
|
144
|
+
client = _client(project_id)
|
|
145
|
+
tables = client.list_tables(f"{project_id}.{dataset_id}")
|
|
146
|
+
return [
|
|
147
|
+
{
|
|
148
|
+
"projectId": project_id,
|
|
149
|
+
"datasetId": dataset_id,
|
|
150
|
+
"tableId": table.table_id,
|
|
151
|
+
"fullTableId": table.full_table_id,
|
|
152
|
+
"tableType": table.table_type,
|
|
153
|
+
}
|
|
154
|
+
for table in tables
|
|
155
|
+
]
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _get_table(project_id: str, dataset_id: str, table_id: str) -> Dict[str, Any]:
|
|
159
|
+
client = _client(project_id)
|
|
160
|
+
table = client.get_table(f"{project_id}.{dataset_id}.{table_id}")
|
|
161
|
+
return {
|
|
162
|
+
"projectId": project_id,
|
|
163
|
+
"datasetId": dataset_id,
|
|
164
|
+
"tableId": table.table_id,
|
|
165
|
+
"fullTableId": table.full_table_id,
|
|
166
|
+
"numRows": int(table.num_rows or 0),
|
|
167
|
+
"location": table.location,
|
|
168
|
+
"schema": _serialize_schema(list(table.schema or [])),
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _get_job(project_id: str, job_id: str, location: Optional[str] = None) -> Dict[str, Any]:
|
|
173
|
+
client = _client(project_id)
|
|
174
|
+
job = client.get_job(job_id, project=project_id, location=location or DEFAULT_LOCATION)
|
|
175
|
+
return {
|
|
176
|
+
"projectId": project_id,
|
|
177
|
+
"job_id": job.job_id,
|
|
178
|
+
"location": job.location,
|
|
179
|
+
"state": job.state,
|
|
180
|
+
"errors": _json_safe(job.errors or []),
|
|
181
|
+
"created": _json_safe(job.created),
|
|
182
|
+
"started": _json_safe(job.started),
|
|
183
|
+
"ended": _json_safe(job.ended),
|
|
184
|
+
"statement_type": getattr(job, "statement_type", None),
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
async def _call_in_thread(func, *args, **kwargs):
|
|
189
|
+
try:
|
|
190
|
+
return await asyncio.to_thread(func, *args, **kwargs)
|
|
191
|
+
except GoogleAPICallError as exc:
|
|
192
|
+
raise RuntimeError(str(exc)) from exc
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
@mcp.tool(name="BigQuery_create_dataset")
|
|
196
|
+
async def bigquery_create_dataset_alias(
|
|
197
|
+
projectId: str,
|
|
198
|
+
datasetId: str,
|
|
199
|
+
location: Optional[str] = None,
|
|
200
|
+
description: Optional[str] = None,
|
|
201
|
+
) -> Dict[str, Any]:
|
|
202
|
+
return await _call_in_thread(
|
|
203
|
+
_create_dataset,
|
|
204
|
+
project_id=projectId,
|
|
205
|
+
dataset_id=datasetId,
|
|
206
|
+
location=location,
|
|
207
|
+
description=description,
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
@mcp.tool(name="BigQuery_execute_sql")
|
|
212
|
+
async def bigquery_execute_sql_alias(
|
|
213
|
+
projectId: str,
|
|
214
|
+
query: str,
|
|
215
|
+
location: Optional[str] = None,
|
|
216
|
+
maxResults: int = 1000,
|
|
217
|
+
) -> Dict[str, Any]:
|
|
218
|
+
return await _call_in_thread(
|
|
219
|
+
_run_query,
|
|
220
|
+
project_id=projectId,
|
|
221
|
+
query=query,
|
|
222
|
+
location=location,
|
|
223
|
+
max_results=maxResults,
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
@mcp.tool(name="BigQuery_execute_write_sql")
|
|
228
|
+
async def bigquery_execute_write_sql_alias(
|
|
229
|
+
projectId: str,
|
|
230
|
+
query: str,
|
|
231
|
+
location: Optional[str] = None,
|
|
232
|
+
) -> Dict[str, Any]:
|
|
233
|
+
return await _call_in_thread(
|
|
234
|
+
_run_query,
|
|
235
|
+
project_id=projectId,
|
|
236
|
+
query=query,
|
|
237
|
+
location=location,
|
|
238
|
+
max_results=1000,
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
@mcp.tool()
|
|
243
|
+
async def bigquery_list_datasets(project_id: Optional[str] = None) -> List[Dict[str, Any]]:
|
|
244
|
+
return await _call_in_thread(_list_datasets, project_id or DEFAULT_PROJECT_ID)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
@mcp.tool()
|
|
248
|
+
async def bigquery_list_tables(dataset_id: str, project_id: Optional[str] = None) -> List[Dict[str, Any]]:
|
|
249
|
+
return await _call_in_thread(_list_tables, project_id or DEFAULT_PROJECT_ID, dataset_id)
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
@mcp.tool()
|
|
253
|
+
async def bigquery_get_table(
|
|
254
|
+
dataset_id: str,
|
|
255
|
+
table_id: str,
|
|
256
|
+
project_id: Optional[str] = None,
|
|
257
|
+
) -> Dict[str, Any]:
|
|
258
|
+
return await _call_in_thread(_get_table, project_id or DEFAULT_PROJECT_ID, dataset_id, table_id)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
@mcp.tool()
|
|
262
|
+
async def bigquery_get_table_schema(
|
|
263
|
+
dataset_id: str,
|
|
264
|
+
table_id: str,
|
|
265
|
+
project_id: Optional[str] = None,
|
|
266
|
+
) -> List[Dict[str, Any]]:
|
|
267
|
+
table = await _call_in_thread(_get_table, project_id or DEFAULT_PROJECT_ID, dataset_id, table_id)
|
|
268
|
+
return table["schema"]
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
@mcp.tool()
|
|
272
|
+
async def bigquery_create_dataset(
|
|
273
|
+
dataset_id: str,
|
|
274
|
+
project_id: Optional[str] = None,
|
|
275
|
+
location: Optional[str] = None,
|
|
276
|
+
description: Optional[str] = None,
|
|
277
|
+
) -> Dict[str, Any]:
|
|
278
|
+
return await _call_in_thread(
|
|
279
|
+
_create_dataset,
|
|
280
|
+
project_id=project_id or DEFAULT_PROJECT_ID,
|
|
281
|
+
dataset_id=dataset_id,
|
|
282
|
+
location=location,
|
|
283
|
+
description=description,
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
@mcp.tool()
|
|
288
|
+
async def bigquery_execute_sql(
|
|
289
|
+
query: str,
|
|
290
|
+
project_id: Optional[str] = None,
|
|
291
|
+
location: Optional[str] = None,
|
|
292
|
+
max_results: int = 1000,
|
|
293
|
+
) -> Dict[str, Any]:
|
|
294
|
+
return await _call_in_thread(
|
|
295
|
+
_run_query,
|
|
296
|
+
project_id=project_id or DEFAULT_PROJECT_ID,
|
|
297
|
+
query=query,
|
|
298
|
+
location=location,
|
|
299
|
+
max_results=max_results,
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
@mcp.tool()
|
|
304
|
+
async def bigquery_execute_write_sql(
|
|
305
|
+
query: str,
|
|
306
|
+
project_id: Optional[str] = None,
|
|
307
|
+
location: Optional[str] = None,
|
|
308
|
+
) -> Dict[str, Any]:
|
|
309
|
+
return await _call_in_thread(
|
|
310
|
+
_run_query,
|
|
311
|
+
project_id=project_id or DEFAULT_PROJECT_ID,
|
|
312
|
+
query=query,
|
|
313
|
+
location=location,
|
|
314
|
+
max_results=1000,
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
@mcp.tool()
|
|
319
|
+
async def bigquery_get_job(job_id: str, project_id: Optional[str] = None, location: Optional[str] = None) -> Dict[str, Any]:
|
|
320
|
+
return await _call_in_thread(_get_job, project_id or DEFAULT_PROJECT_ID, job_id, location)
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def main() -> None:
|
|
324
|
+
print(f"Starting BigQuery MCP on http://{HOST}:{PORT}/mcp")
|
|
325
|
+
print(f"- BIGQUERY_API_URL : {BIGQUERY_API_URL}")
|
|
326
|
+
print(f"- BIGQUERY_GRPC : {BIGQUERY_GRPC_TARGET}")
|
|
327
|
+
print(f"- DEFAULT_PROJECT : {DEFAULT_PROJECT_ID}")
|
|
328
|
+
print(f"- DEFAULT_LOCATION : {DEFAULT_LOCATION}")
|
|
329
|
+
mcp.run(transport="http", host=HOST, port=PORT)
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
if __name__ == "__main__":
|
|
333
|
+
main()
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
"""Booking.com MCP Server.
|
|
2
|
+
|
|
3
|
+
Thin FastMCP wrapper over the Booking.com sandbox REST API
|
|
4
|
+
(``dt_arena/envs/booking/api``). Every tool maps 1:1 to a REST endpoint.
|
|
5
|
+
|
|
6
|
+
Tool surface (``search_properties``, ``get_property``, ``book_property``,
|
|
7
|
+
``list_bookings``, ``list_destinations``) matches the community convention
|
|
8
|
+
for hotel booking MCP servers.
|
|
9
|
+
|
|
10
|
+
Auth model: Bearer access_token from ``login`` / ``register``. Booking can
|
|
11
|
+
also be performed anonymously with ``guest_first_name``, ``guest_last_name``,
|
|
12
|
+
and ``contact_email``.
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import asyncio
|
|
17
|
+
import os
|
|
18
|
+
from typing import Any, Dict, Optional
|
|
19
|
+
|
|
20
|
+
import httpx
|
|
21
|
+
from fastmcp import FastMCP
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
API_URL = os.getenv("BOOKING_API_URL", "http://127.0.0.1:8059").rstrip("/")
|
|
25
|
+
DEFAULT_TOKEN: Optional[str] = (
|
|
26
|
+
os.getenv("BOOKING_USER_ACCESS_TOKEN")
|
|
27
|
+
or os.getenv("USER_ACCESS_TOKEN")
|
|
28
|
+
or os.getenv("ACCESS_TOKEN")
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
async def _req(method, path, *, token=None, params=None, json=None):
|
|
33
|
+
headers: Dict[str, str] = {}
|
|
34
|
+
t = token if token is not None else DEFAULT_TOKEN
|
|
35
|
+
if t:
|
|
36
|
+
headers["authorization"] = f"Bearer {t}"
|
|
37
|
+
if json is not None:
|
|
38
|
+
json = {k: v for k, v in json.items() if v is not None}
|
|
39
|
+
if params:
|
|
40
|
+
params = {k: v for k, v in params.items() if v is not None}
|
|
41
|
+
|
|
42
|
+
last_exc: Optional[Exception] = None
|
|
43
|
+
for attempt in range(1, 6):
|
|
44
|
+
try:
|
|
45
|
+
async with httpx.AsyncClient(timeout=30) as client:
|
|
46
|
+
resp = await client.request(
|
|
47
|
+
method, f"{API_URL}{path}",
|
|
48
|
+
params=params,
|
|
49
|
+
json=json if json is not None else None,
|
|
50
|
+
headers=headers,
|
|
51
|
+
)
|
|
52
|
+
if resp.status_code >= 500:
|
|
53
|
+
last_exc = RuntimeError(f"{resp.status_code} {resp.text[:200]}")
|
|
54
|
+
await asyncio.sleep(min(0.5 * attempt, 3.0))
|
|
55
|
+
continue
|
|
56
|
+
if resp.status_code >= 400:
|
|
57
|
+
try:
|
|
58
|
+
payload = resp.json()
|
|
59
|
+
except Exception:
|
|
60
|
+
payload = {"error": resp.text}
|
|
61
|
+
raise RuntimeError(f"Booking API {resp.status_code}: {payload}")
|
|
62
|
+
if not resp.content:
|
|
63
|
+
return None
|
|
64
|
+
try:
|
|
65
|
+
return resp.json()
|
|
66
|
+
except Exception:
|
|
67
|
+
return resp.text
|
|
68
|
+
except httpx.TransportError as e:
|
|
69
|
+
last_exc = e
|
|
70
|
+
await asyncio.sleep(min(0.5 * attempt, 3.0))
|
|
71
|
+
raise RuntimeError(f"Booking API call failed after retries: {last_exc}")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
mcp = FastMCP("Booking.com MCP Server (Local Sandbox)")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
# ---------------------------------------------------------------------------
|
|
78
|
+
# Auth
|
|
79
|
+
# ---------------------------------------------------------------------------
|
|
80
|
+
|
|
81
|
+
@mcp.tool()
|
|
82
|
+
async def register(
|
|
83
|
+
email: str,
|
|
84
|
+
password: str,
|
|
85
|
+
first_name: str,
|
|
86
|
+
last_name: str,
|
|
87
|
+
genius_level: Optional[int] = None,
|
|
88
|
+
) -> Dict[str, Any]:
|
|
89
|
+
"""Create a new Booking.com Genius account."""
|
|
90
|
+
return await _req("POST", "/api/auth/register", json={
|
|
91
|
+
"email": email, "password": password,
|
|
92
|
+
"first_name": first_name, "last_name": last_name,
|
|
93
|
+
"genius_level": genius_level,
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@mcp.tool()
|
|
98
|
+
async def login(email: str, password: str) -> Dict[str, Any]:
|
|
99
|
+
"""Log in and cache the returned access_token."""
|
|
100
|
+
global DEFAULT_TOKEN
|
|
101
|
+
res = await _req("POST", "/api/auth/login",
|
|
102
|
+
json={"email": email, "password": password})
|
|
103
|
+
if isinstance(res, dict) and res.get("access_token"):
|
|
104
|
+
DEFAULT_TOKEN = res["access_token"]
|
|
105
|
+
return res
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@mcp.tool()
|
|
109
|
+
async def get_me(access_token: Optional[str] = None) -> Dict[str, Any]:
|
|
110
|
+
"""Return the authenticated user's profile."""
|
|
111
|
+
return await _req("GET", "/api/auth/me", token=access_token)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
# ---------------------------------------------------------------------------
|
|
115
|
+
# Destinations & properties
|
|
116
|
+
# ---------------------------------------------------------------------------
|
|
117
|
+
|
|
118
|
+
@mcp.tool()
|
|
119
|
+
async def list_destinations(search: Optional[str] = None) -> Dict[str, Any]:
|
|
120
|
+
"""List destinations, optionally filtered by ``search`` text."""
|
|
121
|
+
return await _req("GET", "/api/destinations", params={"search": search})
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@mcp.tool()
|
|
125
|
+
async def get_destination(destination_id: str) -> Dict[str, Any]:
|
|
126
|
+
"""Fetch one destination by id."""
|
|
127
|
+
return await _req("GET", f"/api/destinations/{destination_id}")
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@mcp.tool()
|
|
131
|
+
async def search_properties(
|
|
132
|
+
destination_id: Optional[str] = None,
|
|
133
|
+
check_in: Optional[str] = None,
|
|
134
|
+
check_out: Optional[str] = None,
|
|
135
|
+
adults: int = 2,
|
|
136
|
+
rooms: int = 1,
|
|
137
|
+
min_price: Optional[int] = None,
|
|
138
|
+
max_price: Optional[int] = None,
|
|
139
|
+
min_rating: Optional[float] = None,
|
|
140
|
+
star_rating: Optional[int] = None,
|
|
141
|
+
is_fully_refundable: Optional[bool] = None,
|
|
142
|
+
is_breakfast_included: Optional[bool] = None,
|
|
143
|
+
is_pay_later: Optional[bool] = None,
|
|
144
|
+
amenities: Optional[str] = None,
|
|
145
|
+
property_type: Optional[str] = None,
|
|
146
|
+
) -> Dict[str, Any]:
|
|
147
|
+
"""Search properties (hotels / apartments / homes).
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
destination_id: From ``list_destinations``.
|
|
151
|
+
check_in / check_out: ``YYYY-MM-DD``.
|
|
152
|
+
adults: Adult guests (default 2).
|
|
153
|
+
rooms: Number of rooms.
|
|
154
|
+
min_price / max_price: Nightly price bounds in USD.
|
|
155
|
+
min_rating: Minimum review rating (0-10 scale).
|
|
156
|
+
star_rating: Exact star rating (1-5).
|
|
157
|
+
is_fully_refundable / is_breakfast_included / is_pay_later: Filter flags.
|
|
158
|
+
amenities: Comma-separated amenity codes.
|
|
159
|
+
property_type: ``'hotel'`` | ``'apartment'`` | ``'home'`` | ``'hostel'``.
|
|
160
|
+
"""
|
|
161
|
+
return await _req("GET", "/api/properties/search", params={
|
|
162
|
+
"destination_id": destination_id,
|
|
163
|
+
"check_in": check_in, "check_out": check_out,
|
|
164
|
+
"adults": adults, "rooms": rooms,
|
|
165
|
+
"min_price": min_price, "max_price": max_price,
|
|
166
|
+
"min_rating": min_rating, "star_rating": star_rating,
|
|
167
|
+
"is_fully_refundable": "true" if is_fully_refundable else None,
|
|
168
|
+
"is_breakfast_included": "true" if is_breakfast_included else None,
|
|
169
|
+
"is_pay_later": "true" if is_pay_later else None,
|
|
170
|
+
"amenities": amenities, "property_type": property_type,
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@mcp.tool()
|
|
175
|
+
async def list_properties(destination_id: Optional[str] = None) -> Dict[str, Any]:
|
|
176
|
+
"""List properties, optionally restricted to a destination."""
|
|
177
|
+
return await _req("GET", "/api/properties", params={
|
|
178
|
+
"destination_id": destination_id,
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
@mcp.tool()
|
|
183
|
+
async def get_property(property_id: str) -> Dict[str, Any]:
|
|
184
|
+
"""Fetch one property's detail bundle (rooms, amenities, reviews)."""
|
|
185
|
+
return await _req("GET", f"/api/properties/{property_id}")
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
# ---------------------------------------------------------------------------
|
|
189
|
+
# Bookings
|
|
190
|
+
# ---------------------------------------------------------------------------
|
|
191
|
+
|
|
192
|
+
@mcp.tool()
|
|
193
|
+
async def book_property(
|
|
194
|
+
property_id: str,
|
|
195
|
+
check_in: str,
|
|
196
|
+
check_out: str,
|
|
197
|
+
guest_first_name: Optional[str] = None,
|
|
198
|
+
guest_last_name: Optional[str] = None,
|
|
199
|
+
contact_email: Optional[str] = None,
|
|
200
|
+
room_id: Optional[str] = None,
|
|
201
|
+
adults: int = 2,
|
|
202
|
+
children: int = 0,
|
|
203
|
+
rooms_count: int = 1,
|
|
204
|
+
contact_phone: Optional[str] = None,
|
|
205
|
+
promo_code: Optional[str] = None,
|
|
206
|
+
access_token: Optional[str] = None,
|
|
207
|
+
) -> Dict[str, Any]:
|
|
208
|
+
"""Book a property stay.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
property_id: From ``search_properties``.
|
|
212
|
+
check_in / check_out: ``YYYY-MM-DD``.
|
|
213
|
+
guest_first_name / guest_last_name / contact_email: Required when
|
|
214
|
+
anonymous (inherited from authenticated user otherwise).
|
|
215
|
+
room_id: Optional specific room id.
|
|
216
|
+
adults / children / rooms_count: Occupancy.
|
|
217
|
+
promo_code: Optional promo code.
|
|
218
|
+
|
|
219
|
+
Returns ``{confirmation_code, booking_id, total_price_usd, status: 'confirmed'}``.
|
|
220
|
+
"""
|
|
221
|
+
return await _req(
|
|
222
|
+
"POST", "/api/bookings", token=access_token,
|
|
223
|
+
json={
|
|
224
|
+
"property_id": property_id, "room_id": room_id,
|
|
225
|
+
"check_in": check_in, "check_out": check_out,
|
|
226
|
+
"adults": adults, "children": children, "rooms_count": rooms_count,
|
|
227
|
+
"guest_first_name": guest_first_name,
|
|
228
|
+
"guest_last_name": guest_last_name,
|
|
229
|
+
"contact_email": contact_email, "contact_phone": contact_phone,
|
|
230
|
+
"promo_code": promo_code,
|
|
231
|
+
},
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
@mcp.tool()
|
|
236
|
+
async def list_bookings(access_token: Optional[str] = None) -> Dict[str, Any]:
|
|
237
|
+
"""List the authenticated user's bookings."""
|
|
238
|
+
return await _req("GET", "/api/bookings", token=access_token)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
@mcp.tool()
|
|
242
|
+
async def lookup_booking(confirmation_code: str,
|
|
243
|
+
last_name: str) -> Dict[str, Any]:
|
|
244
|
+
"""Anonymous booking lookup by confirmation code + guest last name."""
|
|
245
|
+
return await _req("GET", "/api/bookings/lookup", params={
|
|
246
|
+
"confirmation_code": confirmation_code, "last_name": last_name,
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
@mcp.tool()
|
|
251
|
+
async def cancel_booking(
|
|
252
|
+
booking_id_or_confirmation: str,
|
|
253
|
+
access_token: Optional[str] = None,
|
|
254
|
+
) -> Dict[str, Any]:
|
|
255
|
+
"""Cancel a booking by internal id or confirmation code."""
|
|
256
|
+
return await _req(
|
|
257
|
+
"POST", f"/api/bookings/{booking_id_or_confirmation}/cancel",
|
|
258
|
+
token=access_token,
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
# ---------------------------------------------------------------------------
|
|
263
|
+
# Promos / homes-love / recent searches
|
|
264
|
+
# ---------------------------------------------------------------------------
|
|
265
|
+
|
|
266
|
+
@mcp.tool()
|
|
267
|
+
async def get_promo(code: str) -> Dict[str, Any]:
|
|
268
|
+
"""Look up a promo code."""
|
|
269
|
+
return await _req("GET", f"/api/promos/{code.upper()}")
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
@mcp.tool()
|
|
273
|
+
async def list_homes_love() -> Dict[str, Any]:
|
|
274
|
+
"""Return the editorial "Homes guests love" shelf."""
|
|
275
|
+
return await _req("GET", "/api/homes-love")
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
@mcp.tool()
|
|
279
|
+
async def list_recent_searches(access_token: Optional[str] = None) -> Dict[str, Any]:
|
|
280
|
+
"""Return the user's recent property searches."""
|
|
281
|
+
return await _req("GET", "/api/recent-searches", token=access_token)
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
@mcp.tool()
|
|
285
|
+
async def save_recent_search(
|
|
286
|
+
destination_id: Optional[str] = None,
|
|
287
|
+
check_in: Optional[str] = None,
|
|
288
|
+
check_out: Optional[str] = None,
|
|
289
|
+
adults: int = 2,
|
|
290
|
+
children: int = 0,
|
|
291
|
+
rooms_count: int = 1,
|
|
292
|
+
session_id: Optional[str] = None,
|
|
293
|
+
access_token: Optional[str] = None,
|
|
294
|
+
) -> Dict[str, Any]:
|
|
295
|
+
"""Persist a search in the user's recent-searches list."""
|
|
296
|
+
return await _req(
|
|
297
|
+
"POST", "/api/recent-searches", token=access_token,
|
|
298
|
+
json={
|
|
299
|
+
"destination_id": destination_id,
|
|
300
|
+
"check_in": check_in, "check_out": check_out,
|
|
301
|
+
"adults": adults, "children": children,
|
|
302
|
+
"rooms_count": rooms_count, "session_id": session_id,
|
|
303
|
+
},
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
if __name__ == "__main__":
|
|
308
|
+
host = os.getenv("HOST", "0.0.0.0")
|
|
309
|
+
port = int(os.getenv("PORT", os.getenv("BOOKING_MCP_PORT", "8878")))
|
|
310
|
+
mcp.run(transport="http", host=host, port=port)
|