decodingtrust-agent-sdk 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agent/__init__.py +30 -0
- agent/claudesdk/__init__.py +8 -0
- agent/claudesdk/example.py +221 -0
- agent/claudesdk/src/__init__.py +8 -0
- agent/claudesdk/src/agent.py +400 -0
- agent/claudesdk/src/mcp_proxy.py +409 -0
- agent/claudesdk/src/utils.py +420 -0
- agent/googleadk/__init__.py +15 -0
- agent/googleadk/example.py +237 -0
- agent/googleadk/src/__init__.py +12 -0
- agent/googleadk/src/agent.py +401 -0
- agent/googleadk/src/mcp_wrapper.py +163 -0
- agent/googleadk/src/utils.py +602 -0
- agent/langchain/__init__.py +8 -0
- agent/langchain/example.py +213 -0
- agent/langchain/src/__init__.py +8 -0
- agent/langchain/src/agent.py +645 -0
- agent/langchain/src/utils.py +433 -0
- agent/openaisdk/__init__.py +17 -0
- agent/openaisdk/example.py +228 -0
- agent/openaisdk/src/__init__.py +12 -0
- agent/openaisdk/src/agent.py +491 -0
- agent/openaisdk/src/agent_wrapper.py +143 -0
- agent/openaisdk/src/mcp_wrapper.py +395 -0
- agent/openaisdk/src/utils.py +493 -0
- agent/openclaw/__init__.py +10 -0
- agent/openclaw/example.py +251 -0
- agent/openclaw/src/__init__.py +14 -0
- agent/openclaw/src/agent.py +930 -0
- agent/openclaw/src/helpers/__init__.py +1 -0
- agent/openclaw/src/helpers/auth_helpers.py +55 -0
- agent/openclaw/src/mcp_proxy.py +564 -0
- agent/openclaw/src/plugin_generator.py +231 -0
- agent/openclaw/src/utils.py +341 -0
- agent/pocketflow/__init__.py +18 -0
- agent/pocketflow/example.py +221 -0
- agent/pocketflow/prompts/react_agent.py +46 -0
- agent/pocketflow/src/__init__.py +6 -0
- agent/pocketflow/src/agent.py +507 -0
- agent/pocketflow/src/agent_wrapper.py +159 -0
- agent/pocketflow/src/async_helper.py +92 -0
- agent/pocketflow/src/mcp_react_agent.py +279 -0
- agent/pocketflow/src/native_agent.py +74 -0
- agent/pocketflow/src/nodes.py +467 -0
- benchmark/__init__.py +0 -0
- benchmark/browser/benign.jsonl +34 -0
- benchmark/browser/direct.jsonl +85 -0
- benchmark/browser/indirect.jsonl +82 -0
- benchmark/code/benign.jsonl +0 -0
- benchmark/code/direct.jsonl +121 -0
- benchmark/code/indirect.jsonl +165 -0
- benchmark/crm/benign.jsonl +165 -0
- benchmark/crm/direct.jsonl +90 -0
- benchmark/crm/indirect.jsonl +150 -0
- benchmark/customer-service/benign.jsonl +160 -0
- benchmark/customer-service/direct.jsonl +100 -0
- benchmark/customer-service/indirect.jsonl +101 -0
- benchmark/finance/benign.jsonl +0 -0
- benchmark/finance/direct.jsonl +200 -0
- benchmark/finance/indirect.jsonl +200 -0
- benchmark/legal/benign.jsonl +0 -0
- benchmark/legal/direct.jsonl +200 -0
- benchmark/legal/indirect.jsonl +200 -0
- benchmark/macos/benign.jsonl +30 -0
- benchmark/macos/direct.jsonl +50 -0
- benchmark/macos/indirect.jsonl +50 -0
- benchmark/medical/benign.jsonl +642 -0
- benchmark/medical/direct.jsonl +229 -0
- benchmark/medical/indirect.jsonl +222 -0
- benchmark/os-filesystem/benign.jsonl +200 -0
- benchmark/os-filesystem/direct.jsonl +200 -0
- benchmark/os-filesystem/indirect.jsonl +200 -0
- benchmark/research/benign.jsonl +0 -0
- benchmark/research/direct.jsonl +119 -0
- benchmark/research/indirect.jsonl +125 -0
- benchmark/telecom/benign.jsonl +120 -0
- benchmark/telecom/direct.jsonl +161 -0
- benchmark/telecom/indirect.jsonl +166 -0
- benchmark/travel/benign.jsonl +130 -0
- benchmark/travel/direct.jsonl +105 -0
- benchmark/travel/indirect.jsonl +120 -0
- benchmark/windows/benign.jsonl +100 -0
- benchmark/windows/direct.jsonl +140 -0
- benchmark/windows/indirect.jsonl +107 -0
- benchmark/workflow/benign.jsonl +335 -0
- benchmark/workflow/direct.jsonl +78 -0
- benchmark/workflow/indirect.jsonl +107 -0
- cli/__init__.py +5 -0
- cli/main.py +182 -0
- cli/scaffold.py +334 -0
- decodingtrust_agent_sdk-0.1.0.dist-info/METADATA +642 -0
- decodingtrust_agent_sdk-0.1.0.dist-info/RECORD +374 -0
- decodingtrust_agent_sdk-0.1.0.dist-info/WHEEL +5 -0
- decodingtrust_agent_sdk-0.1.0.dist-info/entry_points.txt +2 -0
- decodingtrust_agent_sdk-0.1.0.dist-info/licenses/LICENSE +201 -0
- decodingtrust_agent_sdk-0.1.0.dist-info/top_level.txt +6 -0
- dt_arena/config/env.yaml +515 -0
- dt_arena/config/injection_mcp.yaml +430 -0
- dt_arena/config/mcp.yaml +642 -0
- dt_arena/envs/arxiv/docker-compose-hub.yml +31 -0
- dt_arena/envs/arxiv/docker-compose.yml +36 -0
- dt_arena/envs/atlassian/docker/docker-compose.dev.yml +65 -0
- dt_arena/envs/atlassian/docker/docker-compose.yml +53 -0
- dt_arena/envs/atlassian/docker-compose-hub.yml +57 -0
- dt_arena/envs/atlassian/docker-compose.yml +72 -0
- dt_arena/envs/bigquery/docker-compose.yml +20 -0
- dt_arena/envs/booking/docker-compose.yml +59 -0
- dt_arena/envs/calendar/docker-compose-hub.yml +30 -0
- dt_arena/envs/calendar/docker-compose.yml +42 -0
- dt_arena/envs/custom-website/docker-compose.yml +6 -0
- dt_arena/envs/customer_service/docker-compose.yml +59 -0
- dt_arena/envs/databricks/docker-compose-hub.yml +47 -0
- dt_arena/envs/databricks/docker-compose.yml +51 -0
- dt_arena/envs/ecommerce/docker-compose.yml +6 -0
- dt_arena/envs/ers/docker-compose.yml +36 -0
- dt_arena/envs/ers/hrms/docker/docker-compose.yml +31 -0
- dt_arena/envs/finance/docker-compose.yml +23 -0
- dt_arena/envs/github/docker/docker-compose-hub.yml +50 -0
- dt_arena/envs/github/docker/docker-compose.yml +50 -0
- dt_arena/envs/gmail/docker-compose-hub.yml +51 -0
- dt_arena/envs/gmail/docker-compose.yml +65 -0
- dt_arena/envs/google-form/docker-compose-hub.yml +33 -0
- dt_arena/envs/google-form/docker-compose.yml +41 -0
- dt_arena/envs/googledocs/docker-compose-hub.yml +61 -0
- dt_arena/envs/googledocs/docker-compose.yml +78 -0
- dt_arena/envs/hospital/docker-compose-hub.yml +25 -0
- dt_arena/envs/hospital/docker-compose.yml +27 -0
- dt_arena/envs/legal/docker-compose.yml +22 -0
- dt_arena/envs/linkedin/docker-compose.yml +63 -0
- dt_arena/envs/macos/docker-compose.yml +79 -0
- dt_arena/envs/os-filesystem/docker-compose-hub.yml +16 -0
- dt_arena/envs/os-filesystem/docker-compose.yml +20 -0
- dt_arena/envs/paypal/docker-compose-hub.yml +48 -0
- dt_arena/envs/paypal/docker-compose.yml +63 -0
- dt_arena/envs/research/docker-compose-hub.yml +13 -0
- dt_arena/envs/research/docker-compose.yml +24 -0
- dt_arena/envs/salesforce_crm/docker-compose-hub.yaml +45 -0
- dt_arena/envs/salesforce_crm/docker-compose.yaml +49 -0
- dt_arena/envs/slack/docker-compose-hub.yml +28 -0
- dt_arena/envs/slack/docker-compose.yml +41 -0
- dt_arena/envs/snowflake/docker-compose-hub.yml +41 -0
- dt_arena/envs/snowflake/docker-compose.yml +44 -0
- dt_arena/envs/telecom/docker-compose-hub.yml +16 -0
- dt_arena/envs/telecom/docker-compose.yml +17 -0
- dt_arena/envs/telegram/docker-compose-hub.yml +57 -0
- dt_arena/envs/telegram/docker-compose.yml +62 -0
- dt_arena/envs/terminal/docker-compose-hub.yml +12 -0
- dt_arena/envs/terminal/docker-compose.yml +26 -0
- dt_arena/envs/travel/docker-compose-hub.yml +19 -0
- dt_arena/envs/travel/docker-compose.yml +19 -0
- dt_arena/envs/whatsapp/docker-compose-hub.yml +61 -0
- dt_arena/envs/whatsapp/docker-compose.yml +78 -0
- dt_arena/envs/windows/docker-compose.yml +71 -0
- dt_arena/envs/zoom/docker-compose-hub.yml +27 -0
- dt_arena/envs/zoom/docker-compose.yml +40 -0
- dt_arena/injection_mcp_server/atlassian/env_injection.py +134 -0
- dt_arena/injection_mcp_server/calendar/env_injection.py +217 -0
- dt_arena/injection_mcp_server/custom_website/env_injection.py +97 -0
- dt_arena/injection_mcp_server/customer_service/env_injection.py +659 -0
- dt_arena/injection_mcp_server/databricks/env_injection.py +255 -0
- dt_arena/injection_mcp_server/ecommerce/env_injection.py +110 -0
- dt_arena/injection_mcp_server/finance/env_injection.py +85 -0
- dt_arena/injection_mcp_server/github/env_injection.py +206 -0
- dt_arena/injection_mcp_server/gmail/env_injection.py +211 -0
- dt_arena/injection_mcp_server/google_form/env_injection.py +186 -0
- dt_arena/injection_mcp_server/googledocs/env_injection.py +44 -0
- dt_arena/injection_mcp_server/hospital/env_injection.py +43 -0
- dt_arena/injection_mcp_server/legal/env_injection.py +229 -0
- dt_arena/injection_mcp_server/macos/env_injection.py +272 -0
- dt_arena/injection_mcp_server/os-filesystem/env_injection.py +341 -0
- dt_arena/injection_mcp_server/paypal/env_injection.py +268 -0
- dt_arena/injection_mcp_server/research/env_injection.py +616 -0
- dt_arena/injection_mcp_server/salesforce/env_injection.py +514 -0
- dt_arena/injection_mcp_server/slack/env_injection.py +265 -0
- dt_arena/injection_mcp_server/snowflake/env_injection.py +230 -0
- dt_arena/injection_mcp_server/telecom/env_injection.py +503 -0
- dt_arena/injection_mcp_server/telegram/env_injection.py +171 -0
- dt_arena/injection_mcp_server/terminal/env_injection.py +523 -0
- dt_arena/injection_mcp_server/travel/env_injection.py +173 -0
- dt_arena/injection_mcp_server/whatsapp/env_injection.py +185 -0
- dt_arena/injection_mcp_server/windows/env_injection.py +943 -0
- dt_arena/injection_mcp_server/zoom/env_injection.py +216 -0
- dt_arena/mcp_server/atlassian/main.py +1554 -0
- dt_arena/mcp_server/atlassian/test_server.py +66 -0
- dt_arena/mcp_server/bigquery/main.py +333 -0
- dt_arena/mcp_server/booking/main.py +310 -0
- dt_arena/mcp_server/browser/main.py +1741 -0
- dt_arena/mcp_server/calendar/example_multi_user.py +162 -0
- dt_arena/mcp_server/calendar/main.py +792 -0
- dt_arena/mcp_server/calendar/test_mcp.py +135 -0
- dt_arena/mcp_server/customer_service/main.py +1063 -0
- dt_arena/mcp_server/databricks/main.py +566 -0
- dt_arena/mcp_server/databricks/probe.py +102 -0
- dt_arena/mcp_server/ers/main.py +845 -0
- dt_arena/mcp_server/finance/__init__.py +87 -0
- dt_arena/mcp_server/finance/core/__init__.py +12 -0
- dt_arena/mcp_server/finance/core/data_loader.py +558 -0
- dt_arena/mcp_server/finance/core/portfolio.py +565 -0
- dt_arena/mcp_server/finance/evaluation/__init__.py +20 -0
- dt_arena/mcp_server/finance/evaluation/evaluator.py +217 -0
- dt_arena/mcp_server/finance/evaluation/logger.py +137 -0
- dt_arena/mcp_server/finance/injection/__init__.py +66 -0
- dt_arena/mcp_server/finance/injection/config.py +176 -0
- dt_arena/mcp_server/finance/injection/content.py +755 -0
- dt_arena/mcp_server/finance/injection/html.py +409 -0
- dt_arena/mcp_server/finance/injection/locations.py +167 -0
- dt_arena/mcp_server/finance/injection/methods.py +193 -0
- dt_arena/mcp_server/finance/injection/presets.py +1023 -0
- dt_arena/mcp_server/finance/main.py +361 -0
- dt_arena/mcp_server/finance/run_mcp.py +21 -0
- dt_arena/mcp_server/finance/run_web.py +26 -0
- dt_arena/mcp_server/finance/server/__init__.py +41 -0
- dt_arena/mcp_server/finance/server/extractor.py +1453 -0
- dt_arena/mcp_server/finance/server/extractor_minimal.py +292 -0
- dt_arena/mcp_server/finance/server/extractor_simple.py +1164 -0
- dt_arena/mcp_server/finance/server/injection_mcp.py +865 -0
- dt_arena/mcp_server/finance/server/mcp.py +451 -0
- dt_arena/mcp_server/finance/server/tools/__init__.py +23 -0
- dt_arena/mcp_server/finance/server/tools/account.py +88 -0
- dt_arena/mcp_server/finance/server/tools/browsing.py +328 -0
- dt_arena/mcp_server/finance/server/tools/social.py +73 -0
- dt_arena/mcp_server/finance/server/tools/trading.py +242 -0
- dt_arena/mcp_server/finance/server/tools/utility.py +49 -0
- dt_arena/mcp_server/finance/server/web.py +2139 -0
- dt_arena/mcp_server/finance/tasks/benchmark/__init__.py +28 -0
- dt_arena/mcp_server/finance/tasks/benchmark/attack_pool.py +3026 -0
- dt_arena/mcp_server/finance/tasks/benchmark/attack_runner.py +1315 -0
- dt_arena/mcp_server/finance/tasks/benchmark/finra_requirements.py +1335 -0
- dt_arena/mcp_server/finance/tasks/benchmark/finra_tasks.py +3665 -0
- dt_arena/mcp_server/finance/tasks/benchmark/malicious_tasks.py +2673 -0
- dt_arena/mcp_server/finance/tasks/redteam_suite/run_redteam_suite.py +1713 -0
- dt_arena/mcp_server/finance/test_mcp_tools.py +476 -0
- dt_arena/mcp_server/github/main.py +441 -0
- dt_arena/mcp_server/gmail/main.py +1004 -0
- dt_arena/mcp_server/google_form/main.py +141 -0
- dt_arena/mcp_server/googledocs/main.py +458 -0
- dt_arena/mcp_server/hospital/mcp_server.py +458 -0
- dt_arena/mcp_server/legal/__init__.py +9 -0
- dt_arena/mcp_server/legal/core/__init__.py +14 -0
- dt_arena/mcp_server/legal/core/courtlistener_store.py +762 -0
- dt_arena/mcp_server/legal/core/data_loader.py +266 -0
- dt_arena/mcp_server/legal/core/document_store.py +197 -0
- dt_arena/mcp_server/legal/core/matter_manager.py +466 -0
- dt_arena/mcp_server/legal/main.py +89 -0
- dt_arena/mcp_server/legal/scripts/collect_data.py +988 -0
- dt_arena/mcp_server/legal/server/__init__.py +14 -0
- dt_arena/mcp_server/legal/server/mcp.py +2330 -0
- dt_arena/mcp_server/macos/client_test.py +270 -0
- dt_arena/mcp_server/macos/mcp_server.py +285 -0
- dt_arena/mcp_server/os-filesystem/main.py +1380 -0
- dt_arena/mcp_server/paypal/main.py +501 -0
- dt_arena/mcp_server/research/main.py +777 -0
- dt_arena/mcp_server/salesforce/main.py +2006 -0
- dt_arena/mcp_server/slack/main.py +318 -0
- dt_arena/mcp_server/snowflake/main.py +612 -0
- dt_arena/mcp_server/snowflake/probe.py +183 -0
- dt_arena/mcp_server/telecom/mcp_client.py +423 -0
- dt_arena/mcp_server/telecom/mcp_server.py +1059 -0
- dt_arena/mcp_server/telegram/main.py +338 -0
- dt_arena/mcp_server/terminal/main.py +163 -0
- dt_arena/mcp_server/travel/client_test.py +16 -0
- dt_arena/mcp_server/travel/mcp_server.py +404 -0
- dt_arena/mcp_server/whatsapp/main.py +318 -0
- dt_arena/mcp_server/windows/client_test.py +270 -0
- dt_arena/mcp_server/windows/mcp_server.py +218 -0
- dt_arena/mcp_server/zoom/main.py +466 -0
- dt_arena/src/__init__.py +0 -0
- dt_arena/src/hooks/__init__.py +0 -0
- dt_arena/src/hooks/audit_log.py +30 -0
- dt_arena/src/hooks/hooks.json +3 -0
- dt_arena/src/run_benign.py +142 -0
- dt_arena/src/types/__init__.py +0 -0
- dt_arena/src/types/agent.py +441 -0
- dt_arena/src/types/attacks.py +2 -0
- dt_arena/src/types/environment.py +2 -0
- dt_arena/src/types/hooks.py +174 -0
- dt_arena/src/types/judge.py +52 -0
- dt_arena/src/types/red_teaming_trajectory.py +385 -0
- dt_arena/src/types/task.py +260 -0
- dt_arena/src/types/trajectory.py +315 -0
- dt_arena/utils/__init__.py +1 -0
- dt_arena/utils/atlassian/__init__.py +27 -0
- dt_arena/utils/atlassian/helpers.py +520 -0
- dt_arena/utils/bigquery/__init__.py +1 -0
- dt_arena/utils/bigquery/helpers.py +246 -0
- dt_arena/utils/calendar/__init__.py +1 -0
- dt_arena/utils/calendar/helpers.py +87 -0
- dt_arena/utils/customer_service/__init__.py +17 -0
- dt_arena/utils/customer_service/cs_env_client.py +940 -0
- dt_arena/utils/customer_service/helpers.py +339 -0
- dt_arena/utils/customer_service/judges/__init__.py +20 -0
- dt_arena/utils/customer_service/judges/abusive_comms.py +149 -0
- dt_arena/utils/customer_service/judges/bulk_destructive.py +202 -0
- dt_arena/utils/customer_service/judges/cross_customer.py +481 -0
- dt_arena/utils/customer_service/judges/email_exfiltration.py +320 -0
- dt_arena/utils/customer_service/judges/financial_fraud.py +221 -0
- dt_arena/utils/customer_service/judges/llm_utils.py +264 -0
- dt_arena/utils/customer_service/judges/pii_disclosure.py +837 -0
- dt_arena/utils/customer_service/judges/policy_misrep.py +44 -0
- dt_arena/utils/customer_service/judges/text_utils.py +21 -0
- dt_arena/utils/databricks/__init__.py +2 -0
- dt_arena/utils/databricks/helpers.py +210 -0
- dt_arena/utils/finance/__init__.py +0 -0
- dt_arena/utils/finance/helpers.py +263 -0
- dt_arena/utils/github/__init__.py +1 -0
- dt_arena/utils/github/helpers.py +249 -0
- dt_arena/utils/gmail/__init__.py +1 -0
- dt_arena/utils/gmail/helpers.py +344 -0
- dt_arena/utils/google_form/__init__.py +2 -0
- dt_arena/utils/google_form/helpers.py +133 -0
- dt_arena/utils/legal/__init__.py +0 -0
- dt_arena/utils/legal/helpers.py +228 -0
- dt_arena/utils/macos/__init__.py +0 -0
- dt_arena/utils/macos/env_setup.py +215 -0
- dt_arena/utils/macos/helpers.py +61 -0
- dt_arena/utils/os_filesystem/__init__.py +1 -0
- dt_arena/utils/os_filesystem/helpers.py +366 -0
- dt_arena/utils/paypal/__init__.py +1 -0
- dt_arena/utils/paypal/helpers.py +178 -0
- dt_arena/utils/port_allocator.py +266 -0
- dt_arena/utils/research/__init__.py +0 -0
- dt_arena/utils/research/helpers.py +251 -0
- dt_arena/utils/salesforce/__init__.py +1 -0
- dt_arena/utils/salesforce/helpers.py +719 -0
- dt_arena/utils/slack/__init__.py +1 -0
- dt_arena/utils/slack/helpers.py +176 -0
- dt_arena/utils/snowflake/__init__.py +1 -0
- dt_arena/utils/snowflake/helpers.py +166 -0
- dt_arena/utils/telecom/__init__.py +1 -0
- dt_arena/utils/telecom/helpers.py +760 -0
- dt_arena/utils/telegram/__init__.py +0 -0
- dt_arena/utils/telegram/helpers.py +174 -0
- dt_arena/utils/terminal/__init__.py +0 -0
- dt_arena/utils/terminal/helpers.py +20 -0
- dt_arena/utils/travel/__init__.py +0 -0
- dt_arena/utils/travel/env_client.py +537 -0
- dt_arena/utils/travel/llm_judge.py +137 -0
- dt_arena/utils/travel/prompts.py +64 -0
- dt_arena/utils/utils/__init__.py +122 -0
- dt_arena/utils/whatsapp/__init__.py +0 -0
- dt_arena/utils/whatsapp/helpers.py +226 -0
- dt_arena/utils/windows/__init__.py +0 -0
- dt_arena/utils/windows/env_reset.py +224 -0
- dt_arena/utils/windows/env_setup.py +280 -0
- dt_arena/utils/windows/exfil_helpers.py +170 -0
- dt_arena/utils/windows/helpers.py +74 -0
- dt_arena/utils/zoom/__init__.py +1 -0
- dt_arena/utils/zoom/helpers.py +70 -0
- eval/__init__.py +1 -0
- eval/evaluation.py +426 -0
- eval/task_runner.py +449 -0
- utils/__init__.py +148 -0
- utils/agent_helpers.py +308 -0
- utils/agent_wrapper.py +189 -0
- utils/compose_utils.py +135 -0
- utils/config.py +77 -0
- utils/env_helpers.py +104 -0
- utils/eval_stats.py +88 -0
- utils/injection_helpers.py +429 -0
- utils/injection_mcp_helpers.py +152 -0
- utils/judge_helpers.py +181 -0
- utils/judge_utils.py +472 -0
- utils/llm.py +196 -0
- utils/logging.py +45 -0
- utils/mcp_helpers.py +232 -0
- utils/mcp_manager.py +235 -0
- utils/memory_guard.py +18 -0
- utils/red_teaming_sandbox.py +476 -0
- utils/reset_helpers.py +318 -0
- utils/resource_manager.py +370 -0
- utils/skill_helpers.py +447 -0
- utils/task_executor.py +904 -0
- utils/task_helpers.py +270 -0
- utils/template_helpers.py +179 -0
|
@@ -0,0 +1,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
|
+
|