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,141 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Local Google Form MCP server
4
+ - Controls the dockerized Google-Form environment at dt_arena/envs/google-form
5
+ - Exposes tools to read/update form schema and list/submit/clear submissions
6
+ """
7
+ import os
8
+ import json
9
+ from pathlib import Path
10
+ from typing import Any, Dict, List, Optional
11
+
12
+ import httpx
13
+ from fastmcp import FastMCP
14
+
15
+ FORM_API_BASE = os.getenv("FORM_API_BASE", "http://127.0.0.1:8054").rstrip("/")
16
+ # Default schema path relative to this file's location
17
+ _default_schema = Path(__file__).resolve().parent.parent.parent / "envs" / "google-form" / "schemas" / "schema.json"
18
+ # Handle empty SCHEMA_PATH env var by using default
19
+ _schema_env = os.getenv("SCHEMA_PATH", "").strip()
20
+ SCHEMA_PATH = Path(_schema_env) if _schema_env else _default_schema
21
+
22
+ mcp = FastMCP("Local Google Form")
23
+
24
+ _client: Optional[httpx.AsyncClient] = None
25
+
26
+
27
+ async def get_http() -> httpx.AsyncClient:
28
+ global _client
29
+ if _client is None:
30
+ _client = httpx.AsyncClient(timeout=20.0)
31
+ return _client
32
+
33
+
34
+ def _read_schema_file() -> Dict[str, Any]:
35
+ if not SCHEMA_PATH.exists():
36
+ raise FileNotFoundError(f"Schema not found at {SCHEMA_PATH}")
37
+ return json.loads(SCHEMA_PATH.read_text(encoding="utf-8"))
38
+
39
+
40
+ def _write_schema_file(data: Dict[str, Any]) -> None:
41
+ SCHEMA_PATH.parent.mkdir(parents=True, exist_ok=True)
42
+ SCHEMA_PATH.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
43
+
44
+
45
+ @mcp.tool()
46
+ async def get_form() -> str:
47
+ """Get current form schema (via API if available, else read file)."""
48
+ try:
49
+ client = await get_http()
50
+ r = await client.get(f"{FORM_API_BASE}/form")
51
+ if r.status_code == 200:
52
+ return json.dumps(r.json(), ensure_ascii=False, indent=2)
53
+ except Exception:
54
+ pass
55
+ # Fallback to file
56
+ try:
57
+ return json.dumps(_read_schema_file(), ensure_ascii=False, indent=2)
58
+ except Exception as e:
59
+ return json.dumps({"error": str(e)})
60
+
61
+
62
+ @mcp.tool()
63
+ async def set_form(schema_json: str) -> str:
64
+ """Replace schema.json with provided JSON string (pretty-printed)."""
65
+ try:
66
+ data = json.loads(schema_json)
67
+ if not isinstance(data, dict):
68
+ return json.dumps({"error": "schema must be a JSON object"})
69
+ _write_schema_file(data)
70
+ return json.dumps({"ok": True, "schema_path": str(SCHEMA_PATH)})
71
+ except Exception as e:
72
+ return json.dumps({"error": str(e)})
73
+
74
+
75
+ @mcp.tool()
76
+ async def list_submissions(limit: int = 100) -> str:
77
+ """List recent submissions from API. Returns up to 'limit' rows."""
78
+ try:
79
+ client = await get_http()
80
+ r = await client.get(f"{FORM_API_BASE}/submissions")
81
+ if r.status_code != 200:
82
+ return json.dumps({"error": f"HTTP {r.status_code}: {r.text}"})
83
+ rows = r.json() or []
84
+ return json.dumps(rows[: max(1, min(2000, limit))], ensure_ascii=False, indent=2)
85
+ except Exception as e:
86
+ return json.dumps({"error": str(e)})
87
+
88
+
89
+ @mcp.tool()
90
+ async def submit(payload_json: str) -> str:
91
+ """Submit a response to the form. payload_json should be a JSON object of field->value."""
92
+ try:
93
+ payload = json.loads(payload_json or "{}")
94
+ if not isinstance(payload, dict):
95
+ return json.dumps({"error": "payload must be a JSON object"})
96
+ client = await get_http()
97
+ r = await client.post(f"{FORM_API_BASE}/submit", json=payload)
98
+ if r.status_code != 200:
99
+ return json.dumps({"error": f"HTTP {r.status_code}: {r.text}"})
100
+ return json.dumps(r.json(), ensure_ascii=False)
101
+ except Exception as e:
102
+ return json.dumps({"error": str(e)})
103
+
104
+
105
+ @mcp.tool()
106
+ async def clear_submissions() -> str:
107
+ """Clear local submissions by truncating the submissions.jsonl file."""
108
+ try:
109
+ # submissions are stored at /app/data in container; API doesn't expose clear.
110
+ # We'll use a local file path if mounted; otherwise, emulate by rewriting an empty file in schema folder's sibling 'server/data'
111
+ # Heuristic local path:
112
+ possible_paths = [
113
+ Path("/scratch/czr/thb/DecodingTrust-Agent/dt_arena/envs/google-form/server/data/submissions.jsonl"),
114
+ Path.cwd() / "server" / "data" / "submissions.jsonl",
115
+ ]
116
+ cleared = []
117
+ for p in possible_paths:
118
+ try:
119
+ p.parent.mkdir(parents=True, exist_ok=True)
120
+ p.write_text("", encoding="utf-8")
121
+ cleared.append(str(p))
122
+ except Exception:
123
+ continue
124
+ return json.dumps({"ok": True, "paths": cleared})
125
+ except Exception as e:
126
+ return json.dumps({"error": str(e)})
127
+
128
+
129
+ def main() -> None:
130
+ import sys
131
+ print("Starting Local Google Form MCP server…", file=sys.stderr)
132
+ host = os.getenv("GF_MCP_HOST", "localhost")
133
+ port_str = os.getenv("PORT", "").strip()
134
+ port = int(port_str) if port_str else 8855
135
+ mcp.run(transport="http", host=host, port=port)
136
+
137
+
138
+ if __name__ == "__main__":
139
+ main()
140
+
141
+
@@ -0,0 +1,458 @@
1
+ """
2
+ Google Docs MCP Server - thin client that proxies to the Google Docs ENV API.
3
+ Independent environment with its own PostgreSQL + FastAPI backend.
4
+ """
5
+ import os
6
+ import sys
7
+ import asyncio
8
+ from typing import Any, Dict, List, Optional
9
+
10
+ from fastmcp import FastMCP
11
+ import httpx
12
+
13
+
14
+ API_URL = os.getenv("GOOGLEDOCS_API_URL", "http://127.0.0.1:8866")
15
+ USER_ACCESS_TOKEN = os.getenv("GOOGLEDOCS_USER_ACCESS_TOKEN", "")
16
+
17
+
18
+ async def _api_call(name: str, arguments: Dict[str, Any]) -> Any:
19
+ """Call the Google Docs ENV API with retry for cold-start tolerance."""
20
+ last_exc: Optional[Exception] = None
21
+ for attempt in range(1, 11):
22
+ try:
23
+ async with httpx.AsyncClient(timeout=20) as client:
24
+ args = dict(arguments or {})
25
+ token = os.getenv("GOOGLEDOCS_USER_ACCESS_TOKEN", USER_ACCESS_TOKEN)
26
+ headers = {"Authorization": f"Bearer {token}"} if token else {}
27
+ resp = await client.post(
28
+ f"{API_URL}/tools/call",
29
+ json={"name": name, "arguments": args},
30
+ headers=headers,
31
+ )
32
+ resp.raise_for_status()
33
+ data = resp.json()
34
+ return data.get("result")
35
+ except Exception as e:
36
+ last_exc = e
37
+ await asyncio.sleep(min(0.5 * attempt, 3.0))
38
+ raise RuntimeError(f"ENV API call failed after retries: {last_exc}")
39
+
40
+
41
+ mcp = FastMCP("Google Docs MCP Server")
42
+
43
+
44
+ # ═════════════════════════════════════════════════════════════════════════════
45
+ # Document Management
46
+ # ═════════════════════════════════════════════════════════════════════════════
47
+
48
+ @mcp.tool()
49
+ async def create_document(title: str = "Untitled document", content: str = "",
50
+ folder_id: Optional[str] = None) -> Dict[str, Any]:
51
+ """Create a new Google Docs document.
52
+
53
+ Args:
54
+ title: Document title (default: "Untitled document")
55
+ content: Initial plain text content
56
+ folder_id: Optional folder to create document in
57
+ """
58
+ return await _api_call("create_document", {"title": title, "content": content, "folder_id": folder_id})
59
+
60
+
61
+ @mcp.tool()
62
+ async def get_document(document_id: str) -> Dict[str, Any]:
63
+ """Get a document by ID, including its full content.
64
+
65
+ Args:
66
+ document_id: The document identifier (REQUIRED)
67
+ """
68
+ return await _api_call("get_document", {"document_id": document_id})
69
+
70
+
71
+ @mcp.tool()
72
+ async def update_document(document_id: str, title: Optional[str] = None,
73
+ content: Optional[str] = None, starred: Optional[bool] = None) -> Dict[str, Any]:
74
+ """Update a document's title, content, or starred status.
75
+
76
+ Args:
77
+ document_id: The document identifier (REQUIRED)
78
+ title: New title
79
+ content: New plain text content (replaces existing)
80
+ starred: Star/unstar the document
81
+ """
82
+ args = {"document_id": document_id}
83
+ if title is not None:
84
+ args["title"] = title
85
+ if content is not None:
86
+ args["content"] = content
87
+ if starred is not None:
88
+ args["starred"] = starred
89
+ return await _api_call("update_document", args)
90
+
91
+
92
+ @mcp.tool()
93
+ async def delete_document(document_id: str, permanent: bool = False) -> Dict[str, Any]:
94
+ """Move document to trash or permanently delete it.
95
+
96
+ Args:
97
+ document_id: The document identifier (REQUIRED)
98
+ permanent: If True, permanently delete; if False, move to trash
99
+ """
100
+ return await _api_call("delete_document", {"document_id": document_id, "permanent": permanent})
101
+
102
+
103
+ @mcp.tool()
104
+ async def restore_document(document_id: str) -> Dict[str, Any]:
105
+ """Restore a document from trash.
106
+
107
+ Args:
108
+ document_id: The trashed document identifier (REQUIRED)
109
+ """
110
+ return await _api_call("restore_document", {"document_id": document_id})
111
+
112
+
113
+ @mcp.tool()
114
+ async def list_documents(limit: int = 50, offset: int = 0, folder_id: Optional[str] = None,
115
+ starred: Optional[bool] = None, trashed: bool = False) -> Dict[str, Any]:
116
+ """List documents accessible to the user.
117
+
118
+ Args:
119
+ limit: Maximum number of documents to return (default 50)
120
+ offset: Pagination offset
121
+ folder_id: Filter by folder
122
+ starred: Filter by starred status
123
+ trashed: If True, list trashed documents
124
+ """
125
+ return await _api_call("list_documents", {
126
+ "limit": limit, "offset": offset, "folder_id": folder_id,
127
+ "starred": starred, "trashed": trashed
128
+ })
129
+
130
+
131
+ @mcp.tool()
132
+ async def search_documents(query: str, limit: int = 50) -> Dict[str, Any]:
133
+ """Search documents by title or content.
134
+
135
+ Args:
136
+ query: Search term (REQUIRED)
137
+ limit: Maximum results
138
+ """
139
+ return await _api_call("search_documents", {"query": query, "limit": limit})
140
+
141
+
142
+ @mcp.tool()
143
+ async def get_recent_documents(limit: int = 20) -> Dict[str, Any]:
144
+ """Get recently accessed documents.
145
+
146
+ Args:
147
+ limit: Maximum number of documents (default 20)
148
+ """
149
+ return await _api_call("get_recent_documents", {"limit": limit})
150
+
151
+
152
+ # ═════════════════════════════════════════════════════════════════════════════
153
+ # Sharing
154
+ # ═════════════════════════════════════════════════════════════════════════════
155
+
156
+ @mcp.tool()
157
+ async def share_document(document_id: str, email: str,
158
+ permission: str = "viewer") -> Dict[str, Any]:
159
+ """Share a document with another user.
160
+
161
+ Args:
162
+ document_id: The document identifier (REQUIRED)
163
+ email: Email address of the user to share with (REQUIRED)
164
+ permission: Permission level - 'viewer', 'commenter', or 'editor' (default: viewer)
165
+ """
166
+ return await _api_call("share_document", {
167
+ "document_id": document_id, "email": email, "permission": permission
168
+ })
169
+
170
+
171
+ @mcp.tool()
172
+ async def update_share(share_id: str, permission: str) -> Dict[str, Any]:
173
+ """Update sharing permission for a user.
174
+
175
+ Args:
176
+ share_id: The share identifier (REQUIRED)
177
+ permission: New permission level - 'viewer', 'commenter', or 'editor' (REQUIRED)
178
+ """
179
+ return await _api_call("update_share", {"share_id": share_id, "permission": permission})
180
+
181
+
182
+ @mcp.tool()
183
+ async def remove_share(share_id: str) -> Dict[str, Any]:
184
+ """Remove a user's access to a document.
185
+
186
+ Args:
187
+ share_id: The share identifier (REQUIRED)
188
+ """
189
+ return await _api_call("remove_share", {"share_id": share_id})
190
+
191
+
192
+ @mcp.tool()
193
+ async def get_document_shares(document_id: str) -> Dict[str, Any]:
194
+ """Get all users a document is shared with.
195
+
196
+ Args:
197
+ document_id: The document identifier (REQUIRED)
198
+ """
199
+ return await _api_call("get_document_shares", {"document_id": document_id})
200
+
201
+
202
+ @mcp.tool()
203
+ async def enable_link_sharing(document_id: str, permission: str = "viewer") -> Dict[str, Any]:
204
+ """Enable link sharing for a document (anyone with link can access).
205
+
206
+ Args:
207
+ document_id: The document identifier (REQUIRED)
208
+ permission: Permission for link access - 'viewer', 'commenter', or 'editor'
209
+ """
210
+ return await _api_call("enable_link_sharing", {"document_id": document_id, "permission": permission})
211
+
212
+
213
+ @mcp.tool()
214
+ async def disable_link_sharing(document_id: str) -> Dict[str, Any]:
215
+ """Disable link sharing for a document.
216
+
217
+ Args:
218
+ document_id: The document identifier (REQUIRED)
219
+ """
220
+ return await _api_call("disable_link_sharing", {"document_id": document_id})
221
+
222
+
223
+ # ═════════════════════════════════════════════════════════════════════════════
224
+ # Comments
225
+ # ═════════════════════════════════════════════════════════════════════════════
226
+
227
+ @mcp.tool()
228
+ async def add_comment(document_id: str, content: str, quoted_text: Optional[str] = None,
229
+ selection_start: Optional[int] = None, selection_end: Optional[int] = None,
230
+ parent_id: Optional[str] = None) -> Dict[str, Any]:
231
+ """Add a comment to a document.
232
+
233
+ Args:
234
+ document_id: The document identifier (REQUIRED)
235
+ content: Comment text (REQUIRED)
236
+ quoted_text: Text being commented on
237
+ selection_start: Start position of selection
238
+ selection_end: End position of selection
239
+ parent_id: Parent comment ID for replies
240
+ """
241
+ return await _api_call("add_comment", {
242
+ "document_id": document_id, "content": content, "quoted_text": quoted_text,
243
+ "selection_start": selection_start, "selection_end": selection_end, "parent_id": parent_id
244
+ })
245
+
246
+
247
+ @mcp.tool()
248
+ async def get_comments(document_id: str, include_resolved: bool = False) -> Dict[str, Any]:
249
+ """Get all comments on a document.
250
+
251
+ Args:
252
+ document_id: The document identifier (REQUIRED)
253
+ include_resolved: Include resolved comments (default: False)
254
+ """
255
+ return await _api_call("get_comments", {"document_id": document_id, "include_resolved": include_resolved})
256
+
257
+
258
+ @mcp.tool()
259
+ async def resolve_comment(comment_id: str) -> Dict[str, Any]:
260
+ """Mark a comment as resolved.
261
+
262
+ Args:
263
+ comment_id: The comment identifier (REQUIRED)
264
+ """
265
+ return await _api_call("resolve_comment", {"comment_id": comment_id})
266
+
267
+
268
+ @mcp.tool()
269
+ async def delete_comment(comment_id: str) -> Dict[str, Any]:
270
+ """Delete a comment.
271
+
272
+ Args:
273
+ comment_id: The comment identifier (REQUIRED)
274
+ """
275
+ return await _api_call("delete_comment", {"comment_id": comment_id})
276
+
277
+
278
+ # ═════════════════════════════════════════════════════════════════════════════
279
+ # Version History
280
+ # ═════════════════════════════════════════════════════════════════════════════
281
+
282
+ @mcp.tool()
283
+ async def create_version(document_id: str, version_name: Optional[str] = None) -> Dict[str, Any]:
284
+ """Create a named version (snapshot) of the document.
285
+
286
+ Args:
287
+ document_id: The document identifier (REQUIRED)
288
+ version_name: Optional name for this version
289
+ """
290
+ return await _api_call("create_version", {"document_id": document_id, "version_name": version_name})
291
+
292
+
293
+ @mcp.tool()
294
+ async def list_versions(document_id: str) -> Dict[str, Any]:
295
+ """List all versions of a document.
296
+
297
+ Args:
298
+ document_id: The document identifier (REQUIRED)
299
+ """
300
+ return await _api_call("list_versions", {"document_id": document_id})
301
+
302
+
303
+ @mcp.tool()
304
+ async def restore_version(document_id: str, version_id: str) -> Dict[str, Any]:
305
+ """Restore document to a previous version.
306
+
307
+ Args:
308
+ document_id: The document identifier (REQUIRED)
309
+ version_id: The version identifier (REQUIRED)
310
+ """
311
+ return await _api_call("restore_version", {"document_id": document_id, "version_id": version_id})
312
+
313
+
314
+ # ═════════════════════════════════════════════════════════════════════════════
315
+ # Folders
316
+ # ═════════════════════════════════════════════════════════════════════════════
317
+
318
+ @mcp.tool()
319
+ async def create_folder(name: str, parent_id: Optional[str] = None,
320
+ color: Optional[str] = None) -> Dict[str, Any]:
321
+ """Create a new folder.
322
+
323
+ Args:
324
+ name: Folder name (REQUIRED)
325
+ parent_id: Parent folder ID for nested folders
326
+ color: Folder color (hex code)
327
+ """
328
+ return await _api_call("create_folder", {"name": name, "parent_id": parent_id, "color": color})
329
+
330
+
331
+ @mcp.tool()
332
+ async def list_folders(parent_id: Optional[str] = None) -> Dict[str, Any]:
333
+ """List folders.
334
+
335
+ Args:
336
+ parent_id: Parent folder ID to list children of (None for root)
337
+ """
338
+ return await _api_call("list_folders", {"parent_id": parent_id})
339
+
340
+
341
+ @mcp.tool()
342
+ async def move_document_to_folder(document_id: str, folder_id: Optional[str] = None) -> Dict[str, Any]:
343
+ """Move a document to a folder.
344
+
345
+ Args:
346
+ document_id: The document identifier (REQUIRED)
347
+ folder_id: Target folder ID (None to move to root)
348
+ """
349
+ return await _api_call("move_document_to_folder", {"document_id": document_id, "folder_id": folder_id})
350
+
351
+
352
+ # ═════════════════════════════════════════════════════════════════════════════
353
+ # Images
354
+ # ═════════════════════════════════════════════════════════════════════════════
355
+
356
+ @mcp.tool()
357
+ async def upload_image(filename: str, document_id: Optional[str] = None,
358
+ data: Optional[str] = None, url: Optional[str] = None,
359
+ mime_type: str = "image/png", width: Optional[int] = None,
360
+ height: Optional[int] = None) -> Dict[str, Any]:
361
+ """Upload an image to use in a document.
362
+
363
+ Args:
364
+ filename: Image filename (REQUIRED)
365
+ document_id: Document to associate image with
366
+ data: Base64-encoded image data
367
+ url: URL of the image (alternative to data)
368
+ mime_type: MIME type (default: image/png)
369
+ width: Image width in pixels
370
+ height: Image height in pixels
371
+ """
372
+ return await _api_call("upload_image", {
373
+ "filename": filename, "document_id": document_id, "data": data, "url": url,
374
+ "mime_type": mime_type, "width": width, "height": height
375
+ })
376
+
377
+
378
+ @mcp.tool()
379
+ async def list_images(document_id: str) -> Dict[str, Any]:
380
+ """List all images in a document.
381
+
382
+ Args:
383
+ document_id: The document identifier (REQUIRED)
384
+ """
385
+ return await _api_call("list_images", {"document_id": document_id})
386
+
387
+
388
+ @mcp.tool()
389
+ async def delete_image(image_id: str) -> Dict[str, Any]:
390
+ """Delete an image.
391
+
392
+ Args:
393
+ image_id: The image identifier (REQUIRED)
394
+ """
395
+ return await _api_call("delete_image", {"image_id": image_id})
396
+
397
+
398
+ # ═════════════════════════════════════════════════════════════════════════════
399
+ # Suggestions (Suggesting Mode)
400
+ # ═════════════════════════════════════════════════════════════════════════════
401
+
402
+ @mcp.tool()
403
+ async def add_suggestion(document_id: str, suggestion_type: str,
404
+ original_text: Optional[str] = None, suggested_text: Optional[str] = None,
405
+ selection_start: Optional[int] = None, selection_end: Optional[int] = None) -> Dict[str, Any]:
406
+ """Add a suggestion to a document (like Google Docs suggesting mode).
407
+
408
+ Args:
409
+ document_id: The document identifier (REQUIRED)
410
+ suggestion_type: Type of suggestion - 'insert', 'delete', 'replace', or 'format' (REQUIRED)
411
+ original_text: Original text being changed
412
+ suggested_text: Suggested replacement text
413
+ selection_start: Start position of the change
414
+ selection_end: End position of the change
415
+ """
416
+ return await _api_call("add_suggestion", {
417
+ "document_id": document_id, "suggestion_type": suggestion_type,
418
+ "original_text": original_text, "suggested_text": suggested_text,
419
+ "selection_start": selection_start, "selection_end": selection_end
420
+ })
421
+
422
+
423
+ @mcp.tool()
424
+ async def review_suggestion(suggestion_id: str, action: str) -> Dict[str, Any]:
425
+ """Accept or reject a suggestion.
426
+
427
+ Args:
428
+ suggestion_id: The suggestion identifier (REQUIRED)
429
+ action: 'accept' or 'reject' (REQUIRED)
430
+ """
431
+ return await _api_call("review_suggestion", {"suggestion_id": suggestion_id, "action": action})
432
+
433
+
434
+ @mcp.tool()
435
+ async def list_suggestions(document_id: str, status: str = "pending") -> Dict[str, Any]:
436
+ """List suggestions on a document.
437
+
438
+ Args:
439
+ document_id: The document identifier (REQUIRED)
440
+ status: Filter by status - 'pending', 'accepted', or 'rejected' (default: pending)
441
+ """
442
+ return await _api_call("list_suggestions", {"document_id": document_id, "status": status})
443
+
444
+
445
+ # ═════════════════════════════════════════════════════════════════════════════
446
+ # Entrypoint
447
+ # ═════════════════════════════════════════════════════════════════════════════
448
+
449
+ def main() -> None:
450
+ print("Starting Google Docs MCP Server (proxying to ENV API)...", file=sys.stderr)
451
+ host = os.getenv("GOOGLEDOCS_MCP_HOST", "localhost")
452
+ port_str = os.getenv("PORT", "").strip() or os.getenv("GOOGLEDOCS_MCP_PORT", "8866")
453
+ port = int(port_str)
454
+ mcp.run(transport="http", host=host, port=port)
455
+
456
+
457
+ if __name__ == "__main__":
458
+ main()