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,328 @@
1
+ """
2
+ Browsing tools for the finance web UI (read-only views).
3
+ These tools return extracted page content from the finance site.
4
+ """
5
+
6
+ from typing import TYPE_CHECKING
7
+ if TYPE_CHECKING:
8
+ from ..mcp import FinanceMCPServer
9
+
10
+
11
+ def register_browsing_tools(server: "FinanceMCPServer"):
12
+ """Register all browsing-related MCP tools."""
13
+ mcp = server.mcp
14
+
15
+ @mcp.tool()
16
+ def browse_stock(symbol: str, section: str = "quote", period: str = "5m", date: str = "") -> str:
17
+ """
18
+ Read a single stock page (quote, news, chart, analysis, profile).
19
+ Use this when you already know the ticker.
20
+
21
+ Args:
22
+ symbol: Stock ticker (e.g., "AAPL", "NVDA", "MSFT")
23
+ section: "quote" | "news" | "chart" | "analysis" | "profile"
24
+ period: Chart interval/period. Intraday: "1m" | "5m" | "15m" | "30m" | "1h". Historical: "5D" | "1M" | "3M" | "6M"
25
+ date: Historical date "YYYY-MM-DD" (optional)
26
+
27
+ Examples:
28
+ browse_stock("AAPL") -> Apple quote and key stats
29
+ browse_stock("NVDA", "analysis") -> Analyst ratings/targets
30
+ browse_stock("TSLA", "chart", "5m") -> Tesla 5-minute intraday chart
31
+ browse_stock("TSLA", "chart", "1M") -> Tesla 1-month daily chart
32
+ """
33
+ section, symbol = section.lower(), symbol.upper()
34
+ # Intraday periods stay lowercase (1m, 5m, 15m, 30m, 1h); historical stay uppercase
35
+ intraday = ['1m', '5m', '15m', '30m', '1h']
36
+ if period.lower() not in intraday:
37
+ period = period.upper()
38
+ server.action_logger.log_action("browse_stock", {
39
+ "symbol": symbol,
40
+ "section": section,
41
+ "period": period,
42
+ "date": date
43
+ })
44
+ urls = {
45
+ "quote": f"/quote/{symbol}/",
46
+ "news": f"/quote/{symbol}/news/",
47
+ "chart": f"/quote/{symbol}/chart/?period={period}",
48
+ "analysis": f"/quote/{symbol}/analysis/",
49
+ "profile": f"/quote/{symbol}/profile/",
50
+ }
51
+ if section not in urls:
52
+ output = "❌ Invalid section. Use: quote, news, chart, analysis, profile."
53
+ output = server._apply_tool_injection("browse_stock", output)
54
+ server.action_logger.log_result(output, "browse_stock")
55
+ return output
56
+ try:
57
+ url = urls[section]
58
+ if date:
59
+ url += ("&" if "?" in url else "?") + f"date={date}"
60
+ response = server.client.get(server._get_url(url))
61
+ response.raise_for_status()
62
+ output = server._extract(response.text, section)
63
+ output = server._apply_tool_injection("browse_stock", output)
64
+ server.action_logger.log_result(output, "browse_stock")
65
+ return output
66
+ except Exception as e:
67
+ output = f"Error: {e}"
68
+ output = server._apply_tool_injection("browse_stock", output)
69
+ server.action_logger.log_result(output, "browse_stock")
70
+ return output
71
+
72
+ @mcp.tool()
73
+ def browse_news(symbol: str = "", limit: int = 10, month: str = "", date: str = "", category: str = "") -> str:
74
+ """
75
+ Browse the news feed (latest articles or stock-specific news).
76
+ Returns a list of articles with IDs; use browse_article to read full text.
77
+
78
+ Args:
79
+ symbol: "" for all news, or ticker for stock-specific news
80
+ limit: Max articles (1-50, default: 10)
81
+ month: Filter "YYYY-MM"
82
+ date: Filter "YYYY-MM-DD"
83
+ category: "earnings" | "analyst" | "insider" | "market"
84
+
85
+ Examples:
86
+ browse_news() -> Latest market news
87
+ browse_news("AAPL", limit=5) -> Apple news list
88
+ browse_news(date="2025-11-15") -> News on specific date
89
+ """
90
+ server.action_logger.log_action("browse_news", {
91
+ "symbol": symbol.upper() if symbol else "",
92
+ "limit": limit,
93
+ "month": month,
94
+ "date": date,
95
+ "category": category
96
+ })
97
+ try:
98
+ if symbol:
99
+ url = f"/quote/{symbol.upper()}/news/?limit={limit}"
100
+ page_type = "news"
101
+ else:
102
+ url = f"/news/?limit={limit}"
103
+ page_type = "news_center"
104
+ if month: url += f"&month={month}"
105
+ if date: url += f"&date={date}"
106
+ if category: url += f"&category={category.lower()}"
107
+ response = server.client.get(server._get_url(url))
108
+ response.raise_for_status()
109
+ output = server._extract(response.text, page_type)
110
+ output = server._apply_tool_injection("browse_news", output)
111
+ server.action_logger.log_result(output, "browse_news")
112
+ return output
113
+ except Exception as e:
114
+ output = f"Error: {e}"
115
+ output = server._apply_tool_injection("browse_news", output)
116
+ server.action_logger.log_result(output, "browse_news")
117
+ return output
118
+
119
+ @mcp.tool()
120
+ def browse_article(symbol: str, article_id: int) -> str:
121
+ """
122
+ Read a full news article (body + comments).
123
+ Article IDs come from browse_news results.
124
+
125
+ Args:
126
+ symbol: Stock ticker
127
+ article_id: Article ID from browse_news results
128
+ """
129
+ server.action_logger.log_action("browse_article", {
130
+ "symbol": symbol.upper(),
131
+ "article_id": article_id
132
+ })
133
+ try:
134
+ response = server.client.get(server._get_url(f"/news/{symbol.upper()}/article/{article_id}/"))
135
+ response.raise_for_status()
136
+ output = server._extract(response.text, "article")
137
+ output = server._apply_tool_injection("browse_article", output)
138
+ server.action_logger.log_result(output, "browse_article")
139
+ return output
140
+ except Exception as e:
141
+ output = f"Error: {e}"
142
+ output = server._apply_tool_injection("browse_article", output)
143
+ server.action_logger.log_result(output, "browse_article")
144
+ return output
145
+
146
+ @mcp.tool()
147
+ def browse_portfolio(section: str = "holdings", symbol: str = "", date: str = "") -> str:
148
+ """
149
+ View portfolio pages: holdings, trade ticket, or orders.
150
+ Note: this does not execute trades (use trade_stock/trade_option).
151
+
152
+ Args:
153
+ section: "holdings" | "trade" | "orders"
154
+ symbol: Pre-fill symbol for trade page
155
+ date: Historical date "YYYY-MM-DD" (optional)
156
+
157
+ Examples:
158
+ browse_portfolio() -> Current holdings and cash
159
+ browse_portfolio("trade", "AAPL") -> Trade ticket for AAPL
160
+ browse_portfolio("orders") -> Order history
161
+ """
162
+ section = section.lower()
163
+ server.action_logger.log_action("browse_portfolio", {
164
+ "section": section,
165
+ "symbol": symbol.upper() if symbol else "",
166
+ "date": date
167
+ })
168
+ try:
169
+ if section == "holdings":
170
+ url, page_type = "/portfolios/", "portfolio"
171
+ elif section == "trade":
172
+ url = f"/portfolios/trade/?symbol={symbol.upper()}" if symbol else "/portfolios/trade/"
173
+ page_type = "trade"
174
+ elif section == "orders":
175
+ url, page_type = "/portfolios/orders/", "orders"
176
+ else:
177
+ output = "❌ Invalid section. Use: holdings, trade, orders."
178
+ output = server._apply_tool_injection("browse_portfolio", output)
179
+ server.action_logger.log_result(output, "browse_portfolio")
180
+ return output
181
+ if date:
182
+ url += ("&" if "?" in url else "?") + f"date={date}"
183
+ response = server.client.get(server._get_url(url))
184
+ response.raise_for_status()
185
+ output = server._extract(response.text, page_type)
186
+ output = server._apply_tool_injection("browse_portfolio", output)
187
+ server.action_logger.log_result(output, "browse_portfolio")
188
+ return output
189
+ except Exception as e:
190
+ output = f"Error: {e}"
191
+ output = server._apply_tool_injection("browse_portfolio", output)
192
+ server.action_logger.log_result(output, "browse_portfolio")
193
+ return output
194
+
195
+ @mcp.tool()
196
+ def browse_options(symbol: str, expiration: str = "", option_type: str = "all", date: str = "") -> str:
197
+ """
198
+ View the options chain for a symbol (read-only).
199
+ Use trade_option to place an order.
200
+
201
+ Args:
202
+ symbol: Stock ticker
203
+ expiration: Expiration date "YYYY-MM-DD"
204
+ option_type: "all" | "calls" | "puts"
205
+ date: Historical date (optional)
206
+
207
+ Examples:
208
+ browse_options("AAPL") -> All AAPL options
209
+ browse_options("NVDA", option_type="calls") -> NVDA calls only
210
+ """
211
+ server.action_logger.log_action("browse_options", {
212
+ "symbol": symbol.upper(),
213
+ "expiration": expiration,
214
+ "option_type": option_type,
215
+ "date": date
216
+ })
217
+ try:
218
+ url = f"/portfolios/options/{symbol.upper()}/"
219
+ params = []
220
+ if expiration: params.append(f"expiration={expiration}")
221
+ if option_type.lower() in ["calls", "puts"]: params.append(f"type={option_type.lower()}")
222
+ if date: params.append(f"date={date}")
223
+ if params: url += "?" + "&".join(params)
224
+ response = server.client.get(server._get_url(url))
225
+ response.raise_for_status()
226
+ output = server._extract(response.text, "options")
227
+ output = server._apply_tool_injection("browse_options", output)
228
+ server.action_logger.log_result(output, "browse_options")
229
+ return output
230
+ except Exception as e:
231
+ output = f"Error: {e}"
232
+ output = server._apply_tool_injection("browse_options", output)
233
+ server.action_logger.log_result(output, "browse_options")
234
+ return output
235
+
236
+ @mcp.tool()
237
+ def browse_markets(category: str = "overview", date: str = "") -> str:
238
+ """
239
+ View market pages (indices and movers).
240
+
241
+ Args:
242
+ category: "overview" | "gainers" | "losers" | "active" | "trending"
243
+ date: Historical date "YYYY-MM-DD" (optional)
244
+ """
245
+ server.action_logger.log_action("browse_markets", {
246
+ "category": category,
247
+ "date": date
248
+ })
249
+ try:
250
+ url = "/markets/"
251
+ params = []
252
+ if category.lower() != "overview": params.append(f"category={category.lower()}")
253
+ if date: params.append(f"date={date}")
254
+ if params: url += "?" + "&".join(params)
255
+ response = server.client.get(server._get_url(url))
256
+ response.raise_for_status()
257
+ output = server._extract(response.text, "markets")
258
+ output = server._apply_tool_injection("browse_markets", output)
259
+ server.action_logger.log_result(output, "browse_markets")
260
+ return output
261
+ except Exception as e:
262
+ output = f"Error: {e}"
263
+ output = server._apply_tool_injection("browse_markets", output)
264
+ server.action_logger.log_result(output, "browse_markets")
265
+ return output
266
+
267
+ @mcp.tool()
268
+ def browse_stock_list(sort: str = "symbol", sector: str = "", date: str = "") -> str:
269
+ """
270
+ View the full list of tradeable stocks.
271
+
272
+ Args:
273
+ sort: "symbol" | "name" | "price" | "change" | "volume"
274
+ sector: "technology" | "healthcare" | "finance" | "consumer" | "energy"
275
+ date: Historical date (optional)
276
+ """
277
+ server.action_logger.log_action("browse_stock_list", {
278
+ "sort": sort,
279
+ "sector": sector,
280
+ "date": date
281
+ })
282
+ try:
283
+ url = "/markets/stocks/"
284
+ params = []
285
+ if sort.lower() != "symbol": params.append(f"sort={sort.lower()}")
286
+ if sector: params.append(f"sector={sector.lower()}")
287
+ if date: params.append(f"date={date}")
288
+ if params: url += "?" + "&".join(params)
289
+ response = server.client.get(server._get_url(url))
290
+ response.raise_for_status()
291
+ output = server._extract(response.text, "stock_list")
292
+ output = server._apply_tool_injection("browse_stock_list", output)
293
+ server.action_logger.log_result(output, "browse_stock_list")
294
+ return output
295
+ except Exception as e:
296
+ output = f"Error: {e}"
297
+ output = server._apply_tool_injection("browse_stock_list", output)
298
+ server.action_logger.log_result(output, "browse_stock_list")
299
+ return output
300
+
301
+ @mcp.tool()
302
+ def search(query: str, search_type: str = "all") -> str:
303
+ """
304
+ Search across stocks and news by keyword, ticker, theme, or concept.
305
+ Supports semantic queries like "AI stocks", "crypto", "EV", "fintech", "FAANG".
306
+ Returns a mixed results page: stock matches + news matches.
307
+
308
+ Args:
309
+ query: Search term (keywords or ticker)
310
+ search_type: "all" | "stocks" | "news"
311
+ """
312
+ server.action_logger.log_action("search", {
313
+ "query": query,
314
+ "search_type": search_type
315
+ })
316
+ try:
317
+ response = server.client.get(server._get_url(f"/search/?q={query}&type={search_type}"))
318
+ response.raise_for_status()
319
+ output = server._extract(response.text, "search")
320
+ output = server._apply_tool_injection("search", output)
321
+ server.action_logger.log_result(output, "search")
322
+ return output
323
+ except Exception as e:
324
+ output = f"Error: {e}"
325
+ output = server._apply_tool_injection("search", output)
326
+ server.action_logger.log_result(output, "search")
327
+ return output
328
+
@@ -0,0 +1,73 @@
1
+ """
2
+ Social tools for interacting with news article comments.
3
+ """
4
+
5
+ from typing import TYPE_CHECKING
6
+ if TYPE_CHECKING:
7
+ from ..mcp import FinanceMCPServer
8
+
9
+
10
+ def register_social_tools(server: "FinanceMCPServer"):
11
+ """Register social interaction tools."""
12
+ mcp = server.mcp
13
+
14
+ @mcp.tool()
15
+ def post_comment(symbol: str, article_id: int, content: str, sentiment: str = "neutral") -> str:
16
+ """
17
+ Post a comment on a news article (social interaction).
18
+ Use browse_news -> browse_article to find a valid article_id.
19
+
20
+ Args:
21
+ symbol: Stock ticker
22
+ article_id: Article ID from browse_article
23
+ content: Comment text
24
+ sentiment: "bullish" | "bearish" | "neutral"
25
+
26
+ Example:
27
+ post_comment("AAPL", 12345, "Great earnings!", "bullish")
28
+ """
29
+ sentiment = sentiment.lower()
30
+ if sentiment not in ["bullish", "bearish", "neutral"]:
31
+ sentiment = "neutral"
32
+
33
+ server.action_logger.log_action("post_comment", {
34
+ "symbol": symbol.upper(),
35
+ "article_id": article_id,
36
+ "content": content[:100],
37
+ "sentiment": sentiment
38
+ })
39
+
40
+ try:
41
+ response = server.client.post(
42
+ f"{server.web_url}/api/comment",
43
+ json={
44
+ "symbol": symbol.upper(),
45
+ "article_id": article_id,
46
+ "content": content,
47
+ "sentiment": sentiment,
48
+ "user": "Agent"
49
+ }
50
+ )
51
+ result = response.json()
52
+
53
+ if result.get("success"):
54
+ comment = result.get("comment", {})
55
+ emoji = "🟢" if sentiment == "bullish" else "🔴" if sentiment == "bearish" else "⚪"
56
+ output = f"""✅ COMMENT POSTED
57
+ Article: {symbol.upper()} #{article_id}
58
+ Sentiment: {emoji} {sentiment.upper()}
59
+ Content: {content[:200]}{'...' if len(content) > 200 else ''}"""
60
+ output = server._apply_tool_injection("post_comment", output)
61
+ server.action_logger.log_result(output, "post_comment")
62
+ return output
63
+ else:
64
+ output = f"❌ Failed: {result.get('error', 'Unknown error')}"
65
+ output = server._apply_tool_injection("post_comment", output)
66
+ server.action_logger.log_result(output, "post_comment")
67
+ return output
68
+ except Exception as e:
69
+ output = f"❌ Error: {e}"
70
+ output = server._apply_tool_injection("post_comment", output)
71
+ server.action_logger.log_result(output, "post_comment")
72
+ return output
73
+
@@ -0,0 +1,242 @@
1
+ """
2
+ Trading tools that execute orders against the finance portfolio API.
3
+ """
4
+
5
+ from typing import TYPE_CHECKING
6
+ if TYPE_CHECKING:
7
+ from ..mcp import FinanceMCPServer
8
+
9
+
10
+ def register_trading_tools(server: "FinanceMCPServer"):
11
+ """Register all trading-related MCP tools."""
12
+ mcp = server.mcp
13
+
14
+ @mcp.tool()
15
+ def trade_stock(action: str, symbol: str, quantity: int, order_type: str = "market", limit_price: float = 0.0) -> str:
16
+ """
17
+ Execute a stock trade via the portfolio API.
18
+
19
+ Market orders execute immediately at the current market price.
20
+ Limit orders execute immediately only if the limit price is favorable
21
+ (buy limit >= market, or sell limit <= market); otherwise the order
22
+ stays pending until the market reaches the limit price.
23
+
24
+ Args:
25
+ action: "buy" or "sell"
26
+ symbol: Stock ticker (e.g., "AAPL", "NVDA")
27
+ quantity: Number of shares
28
+ order_type: "market" | "limit"
29
+ limit_price: Required for limit orders (ignored for market)
30
+
31
+ Examples:
32
+ trade_stock("buy", "AAPL", 10) -> Buy 10 AAPL at market
33
+ trade_stock("sell", "AAPL", 10, "limit", 200.0) -> Limit sell at $200 (pending if market < $200)
34
+ """
35
+ action, order_type = action.lower(), order_type.lower()
36
+
37
+ if action not in ["buy", "sell"]:
38
+ output = "❌ Invalid action. Use 'buy' or 'sell'."
39
+ output = server._apply_tool_injection("trade_stock", output)
40
+ server.action_logger.log_result(output, "trade_stock")
41
+ return output
42
+ if order_type not in ["market", "limit"]:
43
+ output = "❌ Invalid order_type. Use 'market' or 'limit'."
44
+ output = server._apply_tool_injection("trade_stock", output)
45
+ server.action_logger.log_result(output, "trade_stock")
46
+ return output
47
+ if order_type == "limit" and limit_price <= 0:
48
+ output = "❌ limit_price must be > 0 for limit orders."
49
+ output = server._apply_tool_injection("trade_stock", output)
50
+ server.action_logger.log_result(output, "trade_stock")
51
+ return output
52
+
53
+ server.action_logger.log_action("trade_stock", {
54
+ "symbol": symbol.upper(),
55
+ "quantity": quantity,
56
+ "action": action,
57
+ "order_type": order_type,
58
+ "limit_price": limit_price if order_type == "limit" else None
59
+ })
60
+
61
+ try:
62
+ payload = {"symbol": symbol.upper(), "shares": quantity}
63
+ if order_type == "limit":
64
+ payload["order_type"] = "limit"
65
+ payload["limit_price"] = limit_price
66
+
67
+ url = f"{server.web_url}/api/portfolio/{action}"
68
+ response = server.client.post(url, json=payload)
69
+ result = response.json()
70
+
71
+ if result.get("success"):
72
+ # Pending limit order (not yet filled)
73
+ if "order" in result and result["order"].get("status") == "pending":
74
+ order = result["order"]
75
+ output = f"""✅ LIMIT ORDER PLACED (PENDING)
76
+ Order ID: {order.get('order_id', 'N/A')}
77
+ {action.upper()} {quantity} shares of {symbol.upper()}
78
+ Limit Price: ${order.get('limit_price', 0):.2f}/share
79
+ Market Price: ${order.get('market_price_at_placement', 0):.2f}/share
80
+ Status: PENDING — will execute when market reaches limit price
81
+ Cash: ${result.get('remaining_cash', 0):.2f}"""
82
+ output = server._apply_tool_injection("trade_stock", output)
83
+ server.action_logger.log_result(output, "trade_stock")
84
+ return output
85
+
86
+ # Executed order (market or immediately fillable limit)
87
+ tx = result.get("transaction", {})
88
+ order_info = f" (LIMIT @ ${limit_price:.2f})" if order_type == "limit" else " (MARKET)"
89
+ if action == "buy":
90
+ output = f"""✅ BUY ORDER EXECUTED{order_info}
91
+ Bought {quantity} shares of {symbol.upper()}
92
+ Price: ${tx.get('price', 0):.2f}/share
93
+ Total: ${tx.get('total', 0):.2f}
94
+ Cash: ${result.get('remaining_cash', 0):.2f}"""
95
+ output = server._apply_tool_injection("trade_stock", output)
96
+ server.action_logger.log_result(output, "trade_stock")
97
+ return output
98
+ else:
99
+ output = f"""✅ SELL ORDER EXECUTED{order_info}
100
+ Sold {quantity} shares of {symbol.upper()}
101
+ Price: ${tx.get('price', 0):.2f}/share
102
+ Proceeds: ${tx.get('total', 0):.2f}
103
+ P&L: ${result.get('pnl', 0):.2f}
104
+ Cash: ${result.get('remaining_cash', 0):.2f}"""
105
+ output = server._apply_tool_injection("trade_stock", output)
106
+ server.action_logger.log_result(output, "trade_stock")
107
+ return output
108
+ else:
109
+ output = f"❌ ORDER FAILED: {result.get('error', 'Unknown error')}"
110
+ output = server._apply_tool_injection("trade_stock", output)
111
+ server.action_logger.log_result(output, "trade_stock")
112
+ return output
113
+ except Exception as e:
114
+ output = f"❌ Error: {e}"
115
+ output = server._apply_tool_injection("trade_stock", output)
116
+ server.action_logger.log_result(output, "trade_stock")
117
+ return output
118
+
119
+ @mcp.tool()
120
+ def trade_option(action: str, symbol: str, option_type: str, strike: float,
121
+ expiration: str, quantity: int, premium: float) -> str:
122
+ """
123
+ Execute an options order immediately via the portfolio API.
124
+
125
+ Args:
126
+ action: "buy" or "sell"
127
+ symbol: Underlying stock (e.g., "AAPL")
128
+ option_type: "call" or "put"
129
+ strike: Strike price
130
+ expiration: Expiration date "YYYY-MM-DD"
131
+ quantity: Number of contracts
132
+ premium: Premium per share
133
+
134
+ Example:
135
+ trade_option("buy", "AAPL", "call", 180.0, "2025-01-17", 1, 5.50)
136
+ """
137
+ action, option_type = action.lower(), option_type.lower()
138
+
139
+ if action not in ["buy", "sell"]:
140
+ output = "❌ Invalid action. Use 'buy' or 'sell'."
141
+ output = server._apply_tool_injection("trade_option", output)
142
+ server.action_logger.log_result(output, "trade_option")
143
+ return output
144
+ if option_type not in ["call", "put"]:
145
+ output = "❌ Invalid option_type. Use 'call' or 'put'."
146
+ output = server._apply_tool_injection("trade_option", output)
147
+ server.action_logger.log_result(output, "trade_option")
148
+ return output
149
+
150
+ server.action_logger.log_action("trade_option", {
151
+ "symbol": symbol.upper(),
152
+ "option_type": option_type,
153
+ "strike": strike,
154
+ "expiration": expiration,
155
+ "quantity": quantity,
156
+ "premium": premium
157
+ })
158
+
159
+ try:
160
+ url = f"{server.web_url}/api/portfolio/{action}_option"
161
+ response = server.client.post(
162
+ url,
163
+ json={
164
+ "symbol": symbol.upper(),
165
+ "option_type": option_type,
166
+ "strike": strike,
167
+ "expiration": expiration,
168
+ "quantity": quantity,
169
+ "premium": premium
170
+ }
171
+ )
172
+ result = response.json()
173
+
174
+ if result.get("success"):
175
+ tx = result.get("transaction", {})
176
+ contract = f"{symbol.upper()} {strike} {option_type.upper()} {expiration}"
177
+ if action == "buy":
178
+ output = f"""✅ OPTION BOUGHT
179
+ {quantity} contract(s): {contract}
180
+ Premium: ${tx.get('premium', 0):.2f}/share
181
+ Total: ${tx.get('total', 0):.2f}
182
+ Cash: ${result.get('remaining_cash', 0):.2f}"""
183
+ output = server._apply_tool_injection("trade_option", output)
184
+ server.action_logger.log_result(output, "trade_option")
185
+ return output
186
+ else:
187
+ output = f"""✅ OPTION SOLD
188
+ {quantity} contract(s): {contract}
189
+ Premium: ${tx.get('premium', 0):.2f}/share
190
+ Proceeds: ${tx.get('total', 0):.2f}
191
+ P&L: ${result.get('pnl', 0):.2f}
192
+ Cash: ${result.get('remaining_cash', 0):.2f}"""
193
+ output = server._apply_tool_injection("trade_option", output)
194
+ server.action_logger.log_result(output, "trade_option")
195
+ return output
196
+ else:
197
+ output = f"❌ ORDER FAILED: {result.get('error', 'Unknown error')}"
198
+ output = server._apply_tool_injection("trade_option", output)
199
+ server.action_logger.log_result(output, "trade_option")
200
+ return output
201
+ except Exception as e:
202
+ output = f"❌ Error: {e}"
203
+ output = server._apply_tool_injection("trade_option", output)
204
+ server.action_logger.log_result(output, "trade_option")
205
+ return output
206
+
207
+ @mcp.tool()
208
+ def cancel_order(order_id: str) -> str:
209
+ """
210
+ Cancel a pending limit order by its order ID.
211
+
212
+ Args:
213
+ order_id: The order ID returned when the limit order was placed.
214
+
215
+ Returns:
216
+ Confirmation or error message.
217
+ """
218
+ server.action_logger.log_action("cancel_order", {"order_id": order_id})
219
+
220
+ try:
221
+ url = f"{server.web_url}/api/portfolio/cancel_order"
222
+ response = server.client.post(url, json={"order_id": order_id})
223
+ result = response.json()
224
+
225
+ if result.get("success"):
226
+ order = result.get("cancelled_order", {})
227
+ output = f"""✅ ORDER CANCELLED
228
+ Order ID: {order_id}
229
+ {order.get('action', '').upper()} {order.get('quantity', 0)} shares of {order.get('symbol', '')}
230
+ Limit Price: ${order.get('limit_price', 0):.2f}/share"""
231
+ else:
232
+ output = f"❌ CANCEL FAILED: {result.get('error', 'Unknown error')}"
233
+
234
+ output = server._apply_tool_injection("cancel_order", output)
235
+ server.action_logger.log_result(output, "cancel_order")
236
+ return output
237
+ except Exception as e:
238
+ output = f"❌ Error: {e}"
239
+ output = server._apply_tool_injection("cancel_order", output)
240
+ server.action_logger.log_result(output, "cancel_order")
241
+ return output
242
+