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.
Files changed (374) hide show
  1. agent/__init__.py +30 -0
  2. agent/claudesdk/__init__.py +8 -0
  3. agent/claudesdk/example.py +221 -0
  4. agent/claudesdk/src/__init__.py +8 -0
  5. agent/claudesdk/src/agent.py +400 -0
  6. agent/claudesdk/src/mcp_proxy.py +409 -0
  7. agent/claudesdk/src/utils.py +420 -0
  8. agent/googleadk/__init__.py +15 -0
  9. agent/googleadk/example.py +237 -0
  10. agent/googleadk/src/__init__.py +12 -0
  11. agent/googleadk/src/agent.py +401 -0
  12. agent/googleadk/src/mcp_wrapper.py +163 -0
  13. agent/googleadk/src/utils.py +602 -0
  14. agent/langchain/__init__.py +8 -0
  15. agent/langchain/example.py +213 -0
  16. agent/langchain/src/__init__.py +8 -0
  17. agent/langchain/src/agent.py +645 -0
  18. agent/langchain/src/utils.py +433 -0
  19. agent/openaisdk/__init__.py +17 -0
  20. agent/openaisdk/example.py +228 -0
  21. agent/openaisdk/src/__init__.py +12 -0
  22. agent/openaisdk/src/agent.py +491 -0
  23. agent/openaisdk/src/agent_wrapper.py +143 -0
  24. agent/openaisdk/src/mcp_wrapper.py +395 -0
  25. agent/openaisdk/src/utils.py +493 -0
  26. agent/openclaw/__init__.py +10 -0
  27. agent/openclaw/example.py +251 -0
  28. agent/openclaw/src/__init__.py +14 -0
  29. agent/openclaw/src/agent.py +930 -0
  30. agent/openclaw/src/helpers/__init__.py +1 -0
  31. agent/openclaw/src/helpers/auth_helpers.py +55 -0
  32. agent/openclaw/src/mcp_proxy.py +564 -0
  33. agent/openclaw/src/plugin_generator.py +231 -0
  34. agent/openclaw/src/utils.py +341 -0
  35. agent/pocketflow/__init__.py +18 -0
  36. agent/pocketflow/example.py +221 -0
  37. agent/pocketflow/prompts/react_agent.py +46 -0
  38. agent/pocketflow/src/__init__.py +6 -0
  39. agent/pocketflow/src/agent.py +507 -0
  40. agent/pocketflow/src/agent_wrapper.py +159 -0
  41. agent/pocketflow/src/async_helper.py +92 -0
  42. agent/pocketflow/src/mcp_react_agent.py +279 -0
  43. agent/pocketflow/src/native_agent.py +74 -0
  44. agent/pocketflow/src/nodes.py +467 -0
  45. benchmark/__init__.py +0 -0
  46. benchmark/browser/benign.jsonl +34 -0
  47. benchmark/browser/direct.jsonl +85 -0
  48. benchmark/browser/indirect.jsonl +82 -0
  49. benchmark/code/benign.jsonl +0 -0
  50. benchmark/code/direct.jsonl +121 -0
  51. benchmark/code/indirect.jsonl +165 -0
  52. benchmark/crm/benign.jsonl +165 -0
  53. benchmark/crm/direct.jsonl +90 -0
  54. benchmark/crm/indirect.jsonl +150 -0
  55. benchmark/customer-service/benign.jsonl +160 -0
  56. benchmark/customer-service/direct.jsonl +100 -0
  57. benchmark/customer-service/indirect.jsonl +101 -0
  58. benchmark/finance/benign.jsonl +0 -0
  59. benchmark/finance/direct.jsonl +200 -0
  60. benchmark/finance/indirect.jsonl +200 -0
  61. benchmark/legal/benign.jsonl +0 -0
  62. benchmark/legal/direct.jsonl +200 -0
  63. benchmark/legal/indirect.jsonl +200 -0
  64. benchmark/macos/benign.jsonl +30 -0
  65. benchmark/macos/direct.jsonl +50 -0
  66. benchmark/macos/indirect.jsonl +50 -0
  67. benchmark/medical/benign.jsonl +642 -0
  68. benchmark/medical/direct.jsonl +229 -0
  69. benchmark/medical/indirect.jsonl +222 -0
  70. benchmark/os-filesystem/benign.jsonl +200 -0
  71. benchmark/os-filesystem/direct.jsonl +200 -0
  72. benchmark/os-filesystem/indirect.jsonl +200 -0
  73. benchmark/research/benign.jsonl +0 -0
  74. benchmark/research/direct.jsonl +119 -0
  75. benchmark/research/indirect.jsonl +125 -0
  76. benchmark/telecom/benign.jsonl +120 -0
  77. benchmark/telecom/direct.jsonl +161 -0
  78. benchmark/telecom/indirect.jsonl +166 -0
  79. benchmark/travel/benign.jsonl +130 -0
  80. benchmark/travel/direct.jsonl +105 -0
  81. benchmark/travel/indirect.jsonl +120 -0
  82. benchmark/windows/benign.jsonl +100 -0
  83. benchmark/windows/direct.jsonl +140 -0
  84. benchmark/windows/indirect.jsonl +107 -0
  85. benchmark/workflow/benign.jsonl +335 -0
  86. benchmark/workflow/direct.jsonl +78 -0
  87. benchmark/workflow/indirect.jsonl +107 -0
  88. cli/__init__.py +5 -0
  89. cli/main.py +182 -0
  90. cli/scaffold.py +334 -0
  91. decodingtrust_agent_sdk-0.1.0.dist-info/METADATA +642 -0
  92. decodingtrust_agent_sdk-0.1.0.dist-info/RECORD +374 -0
  93. decodingtrust_agent_sdk-0.1.0.dist-info/WHEEL +5 -0
  94. decodingtrust_agent_sdk-0.1.0.dist-info/entry_points.txt +2 -0
  95. decodingtrust_agent_sdk-0.1.0.dist-info/licenses/LICENSE +201 -0
  96. decodingtrust_agent_sdk-0.1.0.dist-info/top_level.txt +6 -0
  97. dt_arena/config/env.yaml +515 -0
  98. dt_arena/config/injection_mcp.yaml +430 -0
  99. dt_arena/config/mcp.yaml +642 -0
  100. dt_arena/envs/arxiv/docker-compose-hub.yml +31 -0
  101. dt_arena/envs/arxiv/docker-compose.yml +36 -0
  102. dt_arena/envs/atlassian/docker/docker-compose.dev.yml +65 -0
  103. dt_arena/envs/atlassian/docker/docker-compose.yml +53 -0
  104. dt_arena/envs/atlassian/docker-compose-hub.yml +57 -0
  105. dt_arena/envs/atlassian/docker-compose.yml +72 -0
  106. dt_arena/envs/bigquery/docker-compose.yml +20 -0
  107. dt_arena/envs/booking/docker-compose.yml +59 -0
  108. dt_arena/envs/calendar/docker-compose-hub.yml +30 -0
  109. dt_arena/envs/calendar/docker-compose.yml +42 -0
  110. dt_arena/envs/custom-website/docker-compose.yml +6 -0
  111. dt_arena/envs/customer_service/docker-compose.yml +59 -0
  112. dt_arena/envs/databricks/docker-compose-hub.yml +47 -0
  113. dt_arena/envs/databricks/docker-compose.yml +51 -0
  114. dt_arena/envs/ecommerce/docker-compose.yml +6 -0
  115. dt_arena/envs/ers/docker-compose.yml +36 -0
  116. dt_arena/envs/ers/hrms/docker/docker-compose.yml +31 -0
  117. dt_arena/envs/finance/docker-compose.yml +23 -0
  118. dt_arena/envs/github/docker/docker-compose-hub.yml +50 -0
  119. dt_arena/envs/github/docker/docker-compose.yml +50 -0
  120. dt_arena/envs/gmail/docker-compose-hub.yml +51 -0
  121. dt_arena/envs/gmail/docker-compose.yml +65 -0
  122. dt_arena/envs/google-form/docker-compose-hub.yml +33 -0
  123. dt_arena/envs/google-form/docker-compose.yml +41 -0
  124. dt_arena/envs/googledocs/docker-compose-hub.yml +61 -0
  125. dt_arena/envs/googledocs/docker-compose.yml +78 -0
  126. dt_arena/envs/hospital/docker-compose-hub.yml +25 -0
  127. dt_arena/envs/hospital/docker-compose.yml +27 -0
  128. dt_arena/envs/legal/docker-compose.yml +22 -0
  129. dt_arena/envs/linkedin/docker-compose.yml +63 -0
  130. dt_arena/envs/macos/docker-compose.yml +79 -0
  131. dt_arena/envs/os-filesystem/docker-compose-hub.yml +16 -0
  132. dt_arena/envs/os-filesystem/docker-compose.yml +20 -0
  133. dt_arena/envs/paypal/docker-compose-hub.yml +48 -0
  134. dt_arena/envs/paypal/docker-compose.yml +63 -0
  135. dt_arena/envs/research/docker-compose-hub.yml +13 -0
  136. dt_arena/envs/research/docker-compose.yml +24 -0
  137. dt_arena/envs/salesforce_crm/docker-compose-hub.yaml +45 -0
  138. dt_arena/envs/salesforce_crm/docker-compose.yaml +49 -0
  139. dt_arena/envs/slack/docker-compose-hub.yml +28 -0
  140. dt_arena/envs/slack/docker-compose.yml +41 -0
  141. dt_arena/envs/snowflake/docker-compose-hub.yml +41 -0
  142. dt_arena/envs/snowflake/docker-compose.yml +44 -0
  143. dt_arena/envs/telecom/docker-compose-hub.yml +16 -0
  144. dt_arena/envs/telecom/docker-compose.yml +17 -0
  145. dt_arena/envs/telegram/docker-compose-hub.yml +57 -0
  146. dt_arena/envs/telegram/docker-compose.yml +62 -0
  147. dt_arena/envs/terminal/docker-compose-hub.yml +12 -0
  148. dt_arena/envs/terminal/docker-compose.yml +26 -0
  149. dt_arena/envs/travel/docker-compose-hub.yml +19 -0
  150. dt_arena/envs/travel/docker-compose.yml +19 -0
  151. dt_arena/envs/whatsapp/docker-compose-hub.yml +61 -0
  152. dt_arena/envs/whatsapp/docker-compose.yml +78 -0
  153. dt_arena/envs/windows/docker-compose.yml +71 -0
  154. dt_arena/envs/zoom/docker-compose-hub.yml +27 -0
  155. dt_arena/envs/zoom/docker-compose.yml +40 -0
  156. dt_arena/injection_mcp_server/atlassian/env_injection.py +134 -0
  157. dt_arena/injection_mcp_server/calendar/env_injection.py +217 -0
  158. dt_arena/injection_mcp_server/custom_website/env_injection.py +97 -0
  159. dt_arena/injection_mcp_server/customer_service/env_injection.py +659 -0
  160. dt_arena/injection_mcp_server/databricks/env_injection.py +255 -0
  161. dt_arena/injection_mcp_server/ecommerce/env_injection.py +110 -0
  162. dt_arena/injection_mcp_server/finance/env_injection.py +85 -0
  163. dt_arena/injection_mcp_server/github/env_injection.py +206 -0
  164. dt_arena/injection_mcp_server/gmail/env_injection.py +211 -0
  165. dt_arena/injection_mcp_server/google_form/env_injection.py +186 -0
  166. dt_arena/injection_mcp_server/googledocs/env_injection.py +44 -0
  167. dt_arena/injection_mcp_server/hospital/env_injection.py +43 -0
  168. dt_arena/injection_mcp_server/legal/env_injection.py +229 -0
  169. dt_arena/injection_mcp_server/macos/env_injection.py +272 -0
  170. dt_arena/injection_mcp_server/os-filesystem/env_injection.py +341 -0
  171. dt_arena/injection_mcp_server/paypal/env_injection.py +268 -0
  172. dt_arena/injection_mcp_server/research/env_injection.py +616 -0
  173. dt_arena/injection_mcp_server/salesforce/env_injection.py +514 -0
  174. dt_arena/injection_mcp_server/slack/env_injection.py +265 -0
  175. dt_arena/injection_mcp_server/snowflake/env_injection.py +230 -0
  176. dt_arena/injection_mcp_server/telecom/env_injection.py +503 -0
  177. dt_arena/injection_mcp_server/telegram/env_injection.py +171 -0
  178. dt_arena/injection_mcp_server/terminal/env_injection.py +523 -0
  179. dt_arena/injection_mcp_server/travel/env_injection.py +173 -0
  180. dt_arena/injection_mcp_server/whatsapp/env_injection.py +185 -0
  181. dt_arena/injection_mcp_server/windows/env_injection.py +943 -0
  182. dt_arena/injection_mcp_server/zoom/env_injection.py +216 -0
  183. dt_arena/mcp_server/atlassian/main.py +1554 -0
  184. dt_arena/mcp_server/atlassian/test_server.py +66 -0
  185. dt_arena/mcp_server/bigquery/main.py +333 -0
  186. dt_arena/mcp_server/booking/main.py +310 -0
  187. dt_arena/mcp_server/browser/main.py +1741 -0
  188. dt_arena/mcp_server/calendar/example_multi_user.py +162 -0
  189. dt_arena/mcp_server/calendar/main.py +792 -0
  190. dt_arena/mcp_server/calendar/test_mcp.py +135 -0
  191. dt_arena/mcp_server/customer_service/main.py +1063 -0
  192. dt_arena/mcp_server/databricks/main.py +566 -0
  193. dt_arena/mcp_server/databricks/probe.py +102 -0
  194. dt_arena/mcp_server/ers/main.py +845 -0
  195. dt_arena/mcp_server/finance/__init__.py +87 -0
  196. dt_arena/mcp_server/finance/core/__init__.py +12 -0
  197. dt_arena/mcp_server/finance/core/data_loader.py +558 -0
  198. dt_arena/mcp_server/finance/core/portfolio.py +565 -0
  199. dt_arena/mcp_server/finance/evaluation/__init__.py +20 -0
  200. dt_arena/mcp_server/finance/evaluation/evaluator.py +217 -0
  201. dt_arena/mcp_server/finance/evaluation/logger.py +137 -0
  202. dt_arena/mcp_server/finance/injection/__init__.py +66 -0
  203. dt_arena/mcp_server/finance/injection/config.py +176 -0
  204. dt_arena/mcp_server/finance/injection/content.py +755 -0
  205. dt_arena/mcp_server/finance/injection/html.py +409 -0
  206. dt_arena/mcp_server/finance/injection/locations.py +167 -0
  207. dt_arena/mcp_server/finance/injection/methods.py +193 -0
  208. dt_arena/mcp_server/finance/injection/presets.py +1023 -0
  209. dt_arena/mcp_server/finance/main.py +361 -0
  210. dt_arena/mcp_server/finance/run_mcp.py +21 -0
  211. dt_arena/mcp_server/finance/run_web.py +26 -0
  212. dt_arena/mcp_server/finance/server/__init__.py +41 -0
  213. dt_arena/mcp_server/finance/server/extractor.py +1453 -0
  214. dt_arena/mcp_server/finance/server/extractor_minimal.py +292 -0
  215. dt_arena/mcp_server/finance/server/extractor_simple.py +1164 -0
  216. dt_arena/mcp_server/finance/server/injection_mcp.py +865 -0
  217. dt_arena/mcp_server/finance/server/mcp.py +451 -0
  218. dt_arena/mcp_server/finance/server/tools/__init__.py +23 -0
  219. dt_arena/mcp_server/finance/server/tools/account.py +88 -0
  220. dt_arena/mcp_server/finance/server/tools/browsing.py +328 -0
  221. dt_arena/mcp_server/finance/server/tools/social.py +73 -0
  222. dt_arena/mcp_server/finance/server/tools/trading.py +242 -0
  223. dt_arena/mcp_server/finance/server/tools/utility.py +49 -0
  224. dt_arena/mcp_server/finance/server/web.py +2139 -0
  225. dt_arena/mcp_server/finance/tasks/benchmark/__init__.py +28 -0
  226. dt_arena/mcp_server/finance/tasks/benchmark/attack_pool.py +3026 -0
  227. dt_arena/mcp_server/finance/tasks/benchmark/attack_runner.py +1315 -0
  228. dt_arena/mcp_server/finance/tasks/benchmark/finra_requirements.py +1335 -0
  229. dt_arena/mcp_server/finance/tasks/benchmark/finra_tasks.py +3665 -0
  230. dt_arena/mcp_server/finance/tasks/benchmark/malicious_tasks.py +2673 -0
  231. dt_arena/mcp_server/finance/tasks/redteam_suite/run_redteam_suite.py +1713 -0
  232. dt_arena/mcp_server/finance/test_mcp_tools.py +476 -0
  233. dt_arena/mcp_server/github/main.py +441 -0
  234. dt_arena/mcp_server/gmail/main.py +1004 -0
  235. dt_arena/mcp_server/google_form/main.py +141 -0
  236. dt_arena/mcp_server/googledocs/main.py +458 -0
  237. dt_arena/mcp_server/hospital/mcp_server.py +458 -0
  238. dt_arena/mcp_server/legal/__init__.py +9 -0
  239. dt_arena/mcp_server/legal/core/__init__.py +14 -0
  240. dt_arena/mcp_server/legal/core/courtlistener_store.py +762 -0
  241. dt_arena/mcp_server/legal/core/data_loader.py +266 -0
  242. dt_arena/mcp_server/legal/core/document_store.py +197 -0
  243. dt_arena/mcp_server/legal/core/matter_manager.py +466 -0
  244. dt_arena/mcp_server/legal/main.py +89 -0
  245. dt_arena/mcp_server/legal/scripts/collect_data.py +988 -0
  246. dt_arena/mcp_server/legal/server/__init__.py +14 -0
  247. dt_arena/mcp_server/legal/server/mcp.py +2330 -0
  248. dt_arena/mcp_server/macos/client_test.py +270 -0
  249. dt_arena/mcp_server/macos/mcp_server.py +285 -0
  250. dt_arena/mcp_server/os-filesystem/main.py +1380 -0
  251. dt_arena/mcp_server/paypal/main.py +501 -0
  252. dt_arena/mcp_server/research/main.py +777 -0
  253. dt_arena/mcp_server/salesforce/main.py +2006 -0
  254. dt_arena/mcp_server/slack/main.py +318 -0
  255. dt_arena/mcp_server/snowflake/main.py +612 -0
  256. dt_arena/mcp_server/snowflake/probe.py +183 -0
  257. dt_arena/mcp_server/telecom/mcp_client.py +423 -0
  258. dt_arena/mcp_server/telecom/mcp_server.py +1059 -0
  259. dt_arena/mcp_server/telegram/main.py +338 -0
  260. dt_arena/mcp_server/terminal/main.py +163 -0
  261. dt_arena/mcp_server/travel/client_test.py +16 -0
  262. dt_arena/mcp_server/travel/mcp_server.py +404 -0
  263. dt_arena/mcp_server/whatsapp/main.py +318 -0
  264. dt_arena/mcp_server/windows/client_test.py +270 -0
  265. dt_arena/mcp_server/windows/mcp_server.py +218 -0
  266. dt_arena/mcp_server/zoom/main.py +466 -0
  267. dt_arena/src/__init__.py +0 -0
  268. dt_arena/src/hooks/__init__.py +0 -0
  269. dt_arena/src/hooks/audit_log.py +30 -0
  270. dt_arena/src/hooks/hooks.json +3 -0
  271. dt_arena/src/run_benign.py +142 -0
  272. dt_arena/src/types/__init__.py +0 -0
  273. dt_arena/src/types/agent.py +441 -0
  274. dt_arena/src/types/attacks.py +2 -0
  275. dt_arena/src/types/environment.py +2 -0
  276. dt_arena/src/types/hooks.py +174 -0
  277. dt_arena/src/types/judge.py +52 -0
  278. dt_arena/src/types/red_teaming_trajectory.py +385 -0
  279. dt_arena/src/types/task.py +260 -0
  280. dt_arena/src/types/trajectory.py +315 -0
  281. dt_arena/utils/__init__.py +1 -0
  282. dt_arena/utils/atlassian/__init__.py +27 -0
  283. dt_arena/utils/atlassian/helpers.py +520 -0
  284. dt_arena/utils/bigquery/__init__.py +1 -0
  285. dt_arena/utils/bigquery/helpers.py +246 -0
  286. dt_arena/utils/calendar/__init__.py +1 -0
  287. dt_arena/utils/calendar/helpers.py +87 -0
  288. dt_arena/utils/customer_service/__init__.py +17 -0
  289. dt_arena/utils/customer_service/cs_env_client.py +940 -0
  290. dt_arena/utils/customer_service/helpers.py +339 -0
  291. dt_arena/utils/customer_service/judges/__init__.py +20 -0
  292. dt_arena/utils/customer_service/judges/abusive_comms.py +149 -0
  293. dt_arena/utils/customer_service/judges/bulk_destructive.py +202 -0
  294. dt_arena/utils/customer_service/judges/cross_customer.py +481 -0
  295. dt_arena/utils/customer_service/judges/email_exfiltration.py +320 -0
  296. dt_arena/utils/customer_service/judges/financial_fraud.py +221 -0
  297. dt_arena/utils/customer_service/judges/llm_utils.py +264 -0
  298. dt_arena/utils/customer_service/judges/pii_disclosure.py +837 -0
  299. dt_arena/utils/customer_service/judges/policy_misrep.py +44 -0
  300. dt_arena/utils/customer_service/judges/text_utils.py +21 -0
  301. dt_arena/utils/databricks/__init__.py +2 -0
  302. dt_arena/utils/databricks/helpers.py +210 -0
  303. dt_arena/utils/finance/__init__.py +0 -0
  304. dt_arena/utils/finance/helpers.py +263 -0
  305. dt_arena/utils/github/__init__.py +1 -0
  306. dt_arena/utils/github/helpers.py +249 -0
  307. dt_arena/utils/gmail/__init__.py +1 -0
  308. dt_arena/utils/gmail/helpers.py +344 -0
  309. dt_arena/utils/google_form/__init__.py +2 -0
  310. dt_arena/utils/google_form/helpers.py +133 -0
  311. dt_arena/utils/legal/__init__.py +0 -0
  312. dt_arena/utils/legal/helpers.py +228 -0
  313. dt_arena/utils/macos/__init__.py +0 -0
  314. dt_arena/utils/macos/env_setup.py +215 -0
  315. dt_arena/utils/macos/helpers.py +61 -0
  316. dt_arena/utils/os_filesystem/__init__.py +1 -0
  317. dt_arena/utils/os_filesystem/helpers.py +366 -0
  318. dt_arena/utils/paypal/__init__.py +1 -0
  319. dt_arena/utils/paypal/helpers.py +178 -0
  320. dt_arena/utils/port_allocator.py +266 -0
  321. dt_arena/utils/research/__init__.py +0 -0
  322. dt_arena/utils/research/helpers.py +251 -0
  323. dt_arena/utils/salesforce/__init__.py +1 -0
  324. dt_arena/utils/salesforce/helpers.py +719 -0
  325. dt_arena/utils/slack/__init__.py +1 -0
  326. dt_arena/utils/slack/helpers.py +176 -0
  327. dt_arena/utils/snowflake/__init__.py +1 -0
  328. dt_arena/utils/snowflake/helpers.py +166 -0
  329. dt_arena/utils/telecom/__init__.py +1 -0
  330. dt_arena/utils/telecom/helpers.py +760 -0
  331. dt_arena/utils/telegram/__init__.py +0 -0
  332. dt_arena/utils/telegram/helpers.py +174 -0
  333. dt_arena/utils/terminal/__init__.py +0 -0
  334. dt_arena/utils/terminal/helpers.py +20 -0
  335. dt_arena/utils/travel/__init__.py +0 -0
  336. dt_arena/utils/travel/env_client.py +537 -0
  337. dt_arena/utils/travel/llm_judge.py +137 -0
  338. dt_arena/utils/travel/prompts.py +64 -0
  339. dt_arena/utils/utils/__init__.py +122 -0
  340. dt_arena/utils/whatsapp/__init__.py +0 -0
  341. dt_arena/utils/whatsapp/helpers.py +226 -0
  342. dt_arena/utils/windows/__init__.py +0 -0
  343. dt_arena/utils/windows/env_reset.py +224 -0
  344. dt_arena/utils/windows/env_setup.py +280 -0
  345. dt_arena/utils/windows/exfil_helpers.py +170 -0
  346. dt_arena/utils/windows/helpers.py +74 -0
  347. dt_arena/utils/zoom/__init__.py +1 -0
  348. dt_arena/utils/zoom/helpers.py +70 -0
  349. eval/__init__.py +1 -0
  350. eval/evaluation.py +426 -0
  351. eval/task_runner.py +449 -0
  352. utils/__init__.py +148 -0
  353. utils/agent_helpers.py +308 -0
  354. utils/agent_wrapper.py +189 -0
  355. utils/compose_utils.py +135 -0
  356. utils/config.py +77 -0
  357. utils/env_helpers.py +104 -0
  358. utils/eval_stats.py +88 -0
  359. utils/injection_helpers.py +429 -0
  360. utils/injection_mcp_helpers.py +152 -0
  361. utils/judge_helpers.py +181 -0
  362. utils/judge_utils.py +472 -0
  363. utils/llm.py +196 -0
  364. utils/logging.py +45 -0
  365. utils/mcp_helpers.py +232 -0
  366. utils/mcp_manager.py +235 -0
  367. utils/memory_guard.py +18 -0
  368. utils/red_teaming_sandbox.py +476 -0
  369. utils/reset_helpers.py +318 -0
  370. utils/resource_manager.py +370 -0
  371. utils/skill_helpers.py +447 -0
  372. utils/task_executor.py +904 -0
  373. utils/task_helpers.py +270 -0
  374. 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)