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,218 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import os
|
|
3
|
+
from textwrap import dedent
|
|
4
|
+
from typing import Literal
|
|
5
|
+
|
|
6
|
+
import click
|
|
7
|
+
import requests
|
|
8
|
+
from fastmcp import FastMCP
|
|
9
|
+
from fastmcp.utilities.types import Image
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
API_BASE_URL = os.environ["WINDOWS_API_URL"]
|
|
13
|
+
|
|
14
|
+
instructions = dedent("""
|
|
15
|
+
Windows MCP client provides tools to interact with Windows desktop through a FastAPI backend service.
|
|
16
|
+
All operations are executed via HTTP requests to the backend server.
|
|
17
|
+
""")
|
|
18
|
+
|
|
19
|
+
mcp = FastMCP(name="windows-mcp-client", instructions=instructions)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def make_api_call(endpoint: str, data: dict) -> dict:
|
|
23
|
+
"""Helper function to make API calls to the FastAPI server"""
|
|
24
|
+
try:
|
|
25
|
+
response = requests.post(f"{API_BASE_URL}{endpoint}", json=data)
|
|
26
|
+
response.raise_for_status()
|
|
27
|
+
return response.json()
|
|
28
|
+
except requests.exceptions.RequestException as e:
|
|
29
|
+
return {"status": "error", "result": f"API call failed: {str(e)}"}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@mcp.tool(
|
|
33
|
+
name="launch",
|
|
34
|
+
description='Launch an application from the Windows Start Menu by name (e.g., "notepad", "calculator", "chrome")',
|
|
35
|
+
)
|
|
36
|
+
def launch_tool(name: str) -> str:
|
|
37
|
+
result = make_api_call("/tools/launch", {"name": name})
|
|
38
|
+
return result.get("result", str(result))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@mcp.tool(name="shell", description="Execute PowerShell commands and return the output with status code")
|
|
42
|
+
def powershell_tool(command: str) -> str:
|
|
43
|
+
result = make_api_call("/tools/powershell", {"command": command})
|
|
44
|
+
return result.get("result", str(result))
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@mcp.tool(
|
|
48
|
+
name="screenshot",
|
|
49
|
+
description="Capture comprehensive desktop state including default language used by user interface, focused/opened applications, interactive UI elements (buttons, text fields, menus), informative content (text, labels, status), and scrollable areas. Optionally includes visual screenshot when use_vision=True. Essential for understanding current desktop context and available UI interactions.",
|
|
50
|
+
)
|
|
51
|
+
def state_tool(use_vision: bool = False):
|
|
52
|
+
result = make_api_call("/tools/state", {"use_vision": use_vision})
|
|
53
|
+
|
|
54
|
+
if result.get("status") == "error":
|
|
55
|
+
return result.get("result", str(result))
|
|
56
|
+
|
|
57
|
+
data = result.get("result", {})
|
|
58
|
+
if isinstance(data, dict):
|
|
59
|
+
state_text = data.get("state", "")
|
|
60
|
+
screenshot_base64 = data.get("screenshot")
|
|
61
|
+
|
|
62
|
+
response = [state_text]
|
|
63
|
+
if screenshot_base64:
|
|
64
|
+
screenshot_bytes = base64.b64decode(screenshot_base64)
|
|
65
|
+
response.append(Image(data=screenshot_bytes, format="png"))
|
|
66
|
+
return response
|
|
67
|
+
return str(data)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@mcp.tool(
|
|
71
|
+
name="clipboard",
|
|
72
|
+
description='Copy text to clipboard or retrieve current clipboard content. Use "copy" mode with text parameter to copy, "paste" mode to retrieve.',
|
|
73
|
+
)
|
|
74
|
+
def clipboard_tool(mode: Literal["copy", "paste"], text: str = None) -> str:
|
|
75
|
+
result = make_api_call("/tools/clipboard", {"mode": mode, "text": text})
|
|
76
|
+
return result.get("result", str(result))
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@mcp.tool(
|
|
80
|
+
name="click",
|
|
81
|
+
description="Click on UI elements at specific coordinates. Supports left/right/middle mouse buttons and single/double/triple clicks. Use coordinates from screenshot output.",
|
|
82
|
+
)
|
|
83
|
+
def click_tool(loc: list[int], button: Literal["left", "right", "middle"] = "left", clicks: int = 1) -> str:
|
|
84
|
+
result = make_api_call("/tools/click", {"loc": loc, "button": button, "clicks": clicks})
|
|
85
|
+
return result.get("result", str(result))
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@mcp.tool(
|
|
89
|
+
name="type",
|
|
90
|
+
description="Type text into input fields, text areas, or focused elements. Optionally provide loc coordinates to click and focus a target element first. If loc is omitted, types into the currently focused element. Set clear=True to replace existing text, False to append.",
|
|
91
|
+
)
|
|
92
|
+
def type_tool(text: str, loc: list[int] = None, clear: bool = False, press_enter: bool = False) -> str:
|
|
93
|
+
if loc is not None:
|
|
94
|
+
result = make_api_call("/tools/type", {"loc": loc, "text": text, "clear": clear, "press_enter": press_enter})
|
|
95
|
+
else:
|
|
96
|
+
# No loc: type into current focus via key tool
|
|
97
|
+
if clear:
|
|
98
|
+
make_api_call("/tools/shortcut", {"shortcut": "ctrl+a"})
|
|
99
|
+
make_api_call("/tools/key", {"key": "backspace"})
|
|
100
|
+
result = make_api_call("/tools/key", {"key": text})
|
|
101
|
+
if press_enter:
|
|
102
|
+
make_api_call("/tools/key", {"key": "enter"})
|
|
103
|
+
return result.get("result", str(result))
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@mcp.tool(
|
|
107
|
+
name="resize",
|
|
108
|
+
description='Resize active application window (e.g., "notepad", "calculator", "chrome", etc.) to specific size (WIDTHxHEIGHT) or move to specific location (X,Y).',
|
|
109
|
+
)
|
|
110
|
+
def resize_tool(size: list[int] = None, loc: list[int] = None) -> str:
|
|
111
|
+
result = make_api_call("/tools/resize", {"size": size, "loc": loc})
|
|
112
|
+
return result.get("result", str(result))
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@mcp.tool(
|
|
116
|
+
name="switch",
|
|
117
|
+
description='Switch to a specific application window (e.g., "notepad", "calculator", "chrome", etc.) and bring to foreground.',
|
|
118
|
+
)
|
|
119
|
+
def switch_tool(name: str) -> str:
|
|
120
|
+
result = make_api_call("/tools/switch", {"name": name})
|
|
121
|
+
return result.get("result", str(result))
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@mcp.tool(
|
|
125
|
+
name="scroll",
|
|
126
|
+
description="Scroll at specific coordinates or current mouse position. Use wheel_times to control scroll amount (1 wheel = ~3-5 lines). Essential for navigating lists, web pages, and long content.",
|
|
127
|
+
)
|
|
128
|
+
def scroll_tool(
|
|
129
|
+
loc: list[int] = None,
|
|
130
|
+
type: Literal["horizontal", "vertical"] = "vertical",
|
|
131
|
+
direction: Literal["up", "down", "left", "right"] = "down",
|
|
132
|
+
wheel_times: int = 1,
|
|
133
|
+
) -> str:
|
|
134
|
+
result = make_api_call(
|
|
135
|
+
"/tools/scroll", {"loc": loc, "type": type, "direction": direction, "wheel_times": wheel_times}
|
|
136
|
+
)
|
|
137
|
+
return result.get("result", str(result))
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@mcp.tool(
|
|
141
|
+
name="drag",
|
|
142
|
+
description="Drag and drop operation from source coordinates to destination coordinates. Useful for moving files, resizing windows, or drag-and-drop interactions.",
|
|
143
|
+
)
|
|
144
|
+
def drag_tool(from_loc: list[int], to_loc: list[int]) -> str:
|
|
145
|
+
result = make_api_call("/tools/drag", {"from_loc": from_loc, "to_loc": to_loc})
|
|
146
|
+
return result.get("result", str(result))
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
@mcp.tool(
|
|
150
|
+
name="move",
|
|
151
|
+
description="Move mouse cursor to specific coordinates without clicking. Useful for hovering over elements or positioning cursor before other actions.",
|
|
152
|
+
)
|
|
153
|
+
def move_tool(to_loc: list[int]) -> str:
|
|
154
|
+
result = make_api_call("/tools/move", {"to_loc": to_loc})
|
|
155
|
+
return result.get("result", str(result))
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@mcp.tool(
|
|
159
|
+
name="key",
|
|
160
|
+
description='Press keys or key combinations. Use "+" to combine keys (e.g., "ctrl+c" for copy, "alt+tab" for app switching, "win+r" for Run dialog). Single keys also supported: "enter", "escape", "tab", "space", "backspace", "delete", arrow keys ("up", "down", "left", "right"), function keys ("f1"-"f12").',
|
|
161
|
+
)
|
|
162
|
+
def key_tool(key: str) -> str:
|
|
163
|
+
if "+" in key:
|
|
164
|
+
result = make_api_call("/tools/shortcut", {"shortcut": key})
|
|
165
|
+
else:
|
|
166
|
+
result = make_api_call("/tools/key", {"key": key})
|
|
167
|
+
return result.get("result", str(result))
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
@mcp.tool(
|
|
171
|
+
name="wait",
|
|
172
|
+
description="Pause execution for specified duration in seconds. Useful for waiting for applications to load, animations to complete, or adding delays between actions.",
|
|
173
|
+
)
|
|
174
|
+
def wait_tool(duration: int) -> str:
|
|
175
|
+
result = make_api_call("/tools/wait", {"duration": duration})
|
|
176
|
+
return result.get("result", str(result))
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
@mcp.tool(
|
|
180
|
+
name="scrape",
|
|
181
|
+
description="Fetch and convert webpage content to markdown format. Provide full URL including protocol (http/https). Returns structured text content suitable for analysis.",
|
|
182
|
+
)
|
|
183
|
+
def scrape_tool(url: str) -> str:
|
|
184
|
+
result = make_api_call("/tools/scrape", {"url": url})
|
|
185
|
+
return result.get("result", str(result))
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
@click.command()
|
|
189
|
+
@click.option(
|
|
190
|
+
"--transport",
|
|
191
|
+
help="The transport layer used by the MCP server.",
|
|
192
|
+
type=click.Choice(["stdio", "sse", "streamable-http"]),
|
|
193
|
+
default="stdio",
|
|
194
|
+
)
|
|
195
|
+
@click.option(
|
|
196
|
+
"--host", help="Host to bind the SSE/Streamable HTTP server.", default="localhost", type=str, show_default=True
|
|
197
|
+
)
|
|
198
|
+
@click.option("--port", help="Port to bind the SSE/Streamable HTTP server.", default=8001, type=int, show_default=True)
|
|
199
|
+
@click.option(
|
|
200
|
+
"--api-url",
|
|
201
|
+
help="URL of the FastAPI backend server. If not provided, uses WINDOWS_API_URL env var.",
|
|
202
|
+
default=None,
|
|
203
|
+
type=str,
|
|
204
|
+
)
|
|
205
|
+
def main(transport, host, port, api_url):
|
|
206
|
+
global API_BASE_URL
|
|
207
|
+
# Command line arg takes priority over env var
|
|
208
|
+
if api_url:
|
|
209
|
+
API_BASE_URL = api_url
|
|
210
|
+
|
|
211
|
+
if transport == "stdio":
|
|
212
|
+
mcp.run()
|
|
213
|
+
else:
|
|
214
|
+
mcp.run(transport=transport, host=host, port=port)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
if __name__ == "__main__":
|
|
218
|
+
main()
|
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import json
|
|
4
|
+
from typing import Optional, List, Dict, Any
|
|
5
|
+
|
|
6
|
+
import httpx
|
|
7
|
+
from fastmcp import FastMCP
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
try:
|
|
10
|
+
import yaml # type: ignore
|
|
11
|
+
except Exception:
|
|
12
|
+
yaml = None
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
ZOOM_API_URL = os.getenv("ZOOM_API_URL", "http://localhost:8033")
|
|
16
|
+
USER_ACCESS_TOKEN = os.getenv("USER_ACCESS_TOKEN", "")
|
|
17
|
+
|
|
18
|
+
print("[Zoom MCP] Starting...", file=sys.stderr)
|
|
19
|
+
print(f"[Zoom MCP] ZOOM_API_URL={ZOOM_API_URL}", file=sys.stderr)
|
|
20
|
+
print(f"[Zoom MCP] USER_ACCESS_TOKEN={(USER_ACCESS_TOKEN[:12] + '...') if USER_ACCESS_TOKEN else 'NONE'}", file=sys.stderr)
|
|
21
|
+
sys.stderr.flush()
|
|
22
|
+
|
|
23
|
+
mcp = FastMCP("Zoom Client (Sandbox)")
|
|
24
|
+
|
|
25
|
+
_client: Optional[httpx.AsyncClient] = None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _port_from_registry(default_port: int) -> int:
|
|
29
|
+
try:
|
|
30
|
+
if yaml is None:
|
|
31
|
+
return default_port
|
|
32
|
+
registry_path = Path(__file__).resolve().parent.parent / "registry.yaml"
|
|
33
|
+
if not registry_path.exists():
|
|
34
|
+
return default_port
|
|
35
|
+
data = yaml.safe_load(registry_path.read_text()) or {}
|
|
36
|
+
service_name = Path(__file__).resolve().parent.name # 'zoom'
|
|
37
|
+
for srv in (data.get("servers") or []):
|
|
38
|
+
if isinstance(srv, dict) and srv.get("name") == service_name:
|
|
39
|
+
env = srv.get("env") or {}
|
|
40
|
+
port_str = str(env.get("PORT") or "").strip().strip('"')
|
|
41
|
+
return int(port_str) if port_str else default_port
|
|
42
|
+
except Exception:
|
|
43
|
+
return default_port
|
|
44
|
+
return default_port
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _auth_headers() -> Dict[str, str]:
|
|
48
|
+
if USER_ACCESS_TOKEN:
|
|
49
|
+
return {"Authorization": f"Bearer {USER_ACCESS_TOKEN}", "Content-Type": "application/json"}
|
|
50
|
+
return {"Content-Type": "application/json"}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
async def _get_client() -> httpx.AsyncClient:
|
|
54
|
+
global _client
|
|
55
|
+
if _client is None:
|
|
56
|
+
_client = httpx.AsyncClient(timeout=30.0, headers=_auth_headers())
|
|
57
|
+
return _client
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# Auth
|
|
61
|
+
@mcp.tool()
|
|
62
|
+
async def zoom_login(email: str, password: str) -> str:
|
|
63
|
+
"""Login and return user token (sandbox).
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
email: Account email
|
|
67
|
+
password: Account password
|
|
68
|
+
"""
|
|
69
|
+
data = {"username": email, "password": password}
|
|
70
|
+
# form-encoded
|
|
71
|
+
async with httpx.AsyncClient(timeout=15.0) as c:
|
|
72
|
+
resp = await c.post(f"{ZOOM_API_URL}/api/v1/auth/login", data=data)
|
|
73
|
+
if resp.status_code != 200:
|
|
74
|
+
return json.dumps({"error": f"HTTP {resp.status_code}: {resp.text}"})
|
|
75
|
+
return json.dumps(resp.json())
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# Meetings
|
|
79
|
+
def _compose_settings(
|
|
80
|
+
settings: Optional[Dict[str, Any]],
|
|
81
|
+
*,
|
|
82
|
+
waiting_room: Optional[bool] = None,
|
|
83
|
+
passcode: Optional[str] = None,
|
|
84
|
+
mute_on_entry: Optional[bool] = None,
|
|
85
|
+
record_on_start: Optional[bool] = None,
|
|
86
|
+
) -> Optional[Dict[str, Any]]:
|
|
87
|
+
merged: Dict[str, Any] = {}
|
|
88
|
+
if isinstance(settings, dict):
|
|
89
|
+
merged.update({k: v for k, v in settings.items() if v is not None})
|
|
90
|
+
if waiting_room is not None:
|
|
91
|
+
merged["waiting_room"] = waiting_room
|
|
92
|
+
if passcode is not None:
|
|
93
|
+
merged["passcode"] = passcode
|
|
94
|
+
if mute_on_entry is not None:
|
|
95
|
+
merged["mute_on_entry"] = mute_on_entry
|
|
96
|
+
if record_on_start is not None:
|
|
97
|
+
merged["record_on_start"] = record_on_start
|
|
98
|
+
return merged or None
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@mcp.tool()
|
|
102
|
+
async def meetings_create(
|
|
103
|
+
topic: str,
|
|
104
|
+
description: Optional[str] = None,
|
|
105
|
+
start_time: Optional[str] = None,
|
|
106
|
+
duration: Optional[int] = None,
|
|
107
|
+
timezone: str = "UTC",
|
|
108
|
+
*,
|
|
109
|
+
waiting_room: Optional[bool] = None,
|
|
110
|
+
passcode: Optional[str] = None,
|
|
111
|
+
mute_on_entry: Optional[bool] = None,
|
|
112
|
+
record_on_start: Optional[bool] = None,
|
|
113
|
+
settings: Optional[Dict[str, Any]] = None,
|
|
114
|
+
) -> str:
|
|
115
|
+
"""Create a meeting.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
topic: Meeting title (REQUIRED)
|
|
119
|
+
description: Optional details
|
|
120
|
+
start_time: RFC3339 or ISO timestamp (e.g., "2025-11-21T10:00:00Z")
|
|
121
|
+
duration: Minutes
|
|
122
|
+
timezone: Timezone (default UTC)
|
|
123
|
+
waiting_room: Enable/disable the waiting room for participants.
|
|
124
|
+
passcode: Optional meeting passcode string.
|
|
125
|
+
mute_on_entry: Whether to mute participants on entry.
|
|
126
|
+
record_on_start: Whether to auto-record when the host joins.
|
|
127
|
+
settings: Raw settings dict merged with the explicit flags above.
|
|
128
|
+
"""
|
|
129
|
+
client = await _get_client()
|
|
130
|
+
meeting_settings = _compose_settings(
|
|
131
|
+
settings,
|
|
132
|
+
waiting_room=waiting_room,
|
|
133
|
+
passcode=passcode,
|
|
134
|
+
mute_on_entry=mute_on_entry,
|
|
135
|
+
record_on_start=record_on_start,
|
|
136
|
+
)
|
|
137
|
+
payload: Dict[str, Any] = {
|
|
138
|
+
"topic": topic,
|
|
139
|
+
"description": description,
|
|
140
|
+
"start_time": start_time,
|
|
141
|
+
"duration": duration,
|
|
142
|
+
"timezone": timezone,
|
|
143
|
+
}
|
|
144
|
+
if meeting_settings:
|
|
145
|
+
payload["settings"] = meeting_settings
|
|
146
|
+
resp = await client.post(f"{ZOOM_API_URL}/api/v1/meetings", json=payload)
|
|
147
|
+
return _resp_json(resp)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@mcp.tool()
|
|
151
|
+
async def meetings_list() -> str:
|
|
152
|
+
"""List meetings for current user."""
|
|
153
|
+
client = await _get_client()
|
|
154
|
+
resp = await client.get(f"{ZOOM_API_URL}/api/v1/meetings")
|
|
155
|
+
return _resp_json(resp)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@mcp.tool()
|
|
159
|
+
async def meetings_get(meeting_id: str) -> str:
|
|
160
|
+
"""Get meeting details.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
meeting_id: Meeting identifier (REQUIRED)
|
|
164
|
+
"""
|
|
165
|
+
client = await _get_client()
|
|
166
|
+
resp = await client.get(f"{ZOOM_API_URL}/api/v1/meetings/{meeting_id}")
|
|
167
|
+
return _resp_json(resp)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
@mcp.tool()
|
|
171
|
+
async def meetings_update(
|
|
172
|
+
meeting_id: str,
|
|
173
|
+
topic: Optional[str] = None,
|
|
174
|
+
description: Optional[str] = None,
|
|
175
|
+
start_time: Optional[str] = None,
|
|
176
|
+
duration: Optional[int] = None,
|
|
177
|
+
timezone: Optional[str] = None,
|
|
178
|
+
*,
|
|
179
|
+
waiting_room: Optional[bool] = None,
|
|
180
|
+
passcode: Optional[str] = None,
|
|
181
|
+
mute_on_entry: Optional[bool] = None,
|
|
182
|
+
record_on_start: Optional[bool] = None,
|
|
183
|
+
settings: Optional[Dict[str, Any]] = None,
|
|
184
|
+
) -> str:
|
|
185
|
+
"""Update meeting fields (partial).
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
meeting_id: Meeting id (REQUIRED)
|
|
189
|
+
topic, description, start_time, duration, timezone: Optional updates
|
|
190
|
+
waiting_room, passcode, mute_on_entry, record_on_start: Optional setting overrides
|
|
191
|
+
settings: Raw settings dict merged with the explicit flags above
|
|
192
|
+
"""
|
|
193
|
+
client = await _get_client()
|
|
194
|
+
body: Dict[str, Any] = {}
|
|
195
|
+
if topic is not None: body["topic"] = topic
|
|
196
|
+
if description is not None: body["description"] = description
|
|
197
|
+
if start_time is not None: body["start_time"] = start_time
|
|
198
|
+
if duration is not None: body["duration"] = duration
|
|
199
|
+
if timezone is not None: body["timezone"] = timezone
|
|
200
|
+
meeting_settings = _compose_settings(
|
|
201
|
+
settings,
|
|
202
|
+
waiting_room=waiting_room,
|
|
203
|
+
passcode=passcode,
|
|
204
|
+
mute_on_entry=mute_on_entry,
|
|
205
|
+
record_on_start=record_on_start,
|
|
206
|
+
)
|
|
207
|
+
if meeting_settings:
|
|
208
|
+
body["settings"] = meeting_settings
|
|
209
|
+
resp = await client.put(f"{ZOOM_API_URL}/api/v1/meetings/{meeting_id}", json=body)
|
|
210
|
+
return _resp_json(resp)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
@mcp.tool()
|
|
214
|
+
async def meetings_delete(meeting_id: str) -> str:
|
|
215
|
+
"""Delete a meeting.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
meeting_id: Meeting id (REQUIRED)
|
|
219
|
+
"""
|
|
220
|
+
client = await _get_client()
|
|
221
|
+
resp = await client.delete(f"{ZOOM_API_URL}/api/v1/meetings/{meeting_id}")
|
|
222
|
+
return _resp_json(resp)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
@mcp.tool()
|
|
226
|
+
async def meetings_start(meeting_id: str) -> str:
|
|
227
|
+
"""Start a meeting (host action).
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
meeting_id: Meeting id (REQUIRED)
|
|
231
|
+
"""
|
|
232
|
+
client = await _get_client()
|
|
233
|
+
resp = await client.post(f"{ZOOM_API_URL}/api/v1/meetings.start", json={"meeting_id": meeting_id})
|
|
234
|
+
return _resp_json(resp)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
@mcp.tool()
|
|
238
|
+
async def meetings_join(meeting_id: str) -> str:
|
|
239
|
+
"""Get a join payload/link for the meeting.
|
|
240
|
+
|
|
241
|
+
Args:
|
|
242
|
+
meeting_id: Meeting id (REQUIRED)
|
|
243
|
+
"""
|
|
244
|
+
client = await _get_client()
|
|
245
|
+
resp = await client.post(f"{ZOOM_API_URL}/api/v1/meetings.join", json={"meeting_id": meeting_id})
|
|
246
|
+
return _resp_json(resp)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
@mcp.tool()
|
|
250
|
+
async def meetings_end(meeting_id: str) -> str:
|
|
251
|
+
"""End a meeting (host action)."""
|
|
252
|
+
client = await _get_client()
|
|
253
|
+
resp = await client.post(f"{ZOOM_API_URL}/api/v1/meetings.end", json={"meeting_id": meeting_id})
|
|
254
|
+
return _resp_json(resp)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
@mcp.tool()
|
|
258
|
+
async def meetings_leave(meeting_id: str) -> str:
|
|
259
|
+
"""Leave a meeting (participant action)."""
|
|
260
|
+
client = await _get_client()
|
|
261
|
+
resp = await client.post(f"{ZOOM_API_URL}/api/v1/meetings.leave", json={"meeting_id": meeting_id})
|
|
262
|
+
return _resp_json(resp)
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
# Invitations / Participants
|
|
266
|
+
@mcp.tool()
|
|
267
|
+
async def invitations_create(meeting_id: str, invitee_email: str) -> str:
|
|
268
|
+
"""Create a meeting invitation for an email address."""
|
|
269
|
+
client = await _get_client()
|
|
270
|
+
resp = await client.post(f"{ZOOM_API_URL}/api/v1/invitations.create", json={"meeting_id": meeting_id, "invitee_email": invitee_email})
|
|
271
|
+
return _resp_json(resp)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
@mcp.tool()
|
|
275
|
+
async def invitations_list(meeting_id: str) -> str:
|
|
276
|
+
"""List invitations for a meeting."""
|
|
277
|
+
client = await _get_client()
|
|
278
|
+
resp = await client.get(f"{ZOOM_API_URL}/api/v1/invitations.list", params={"meeting_id": meeting_id})
|
|
279
|
+
return _resp_json(resp)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
@mcp.tool()
|
|
283
|
+
async def invitations_accept(invitation_id: str) -> str:
|
|
284
|
+
"""Accept an invitation by id."""
|
|
285
|
+
client = await _get_client()
|
|
286
|
+
resp = await client.post(f"{ZOOM_API_URL}/api/v1/invitations.accept", json={"invitation_id": invitation_id})
|
|
287
|
+
return _resp_json(resp)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
@mcp.tool()
|
|
291
|
+
async def invitations_decline(invitation_id: str) -> str:
|
|
292
|
+
"""Decline an invitation by id."""
|
|
293
|
+
client = await _get_client()
|
|
294
|
+
resp = await client.post(f"{ZOOM_API_URL}/api/v1/invitations.decline", json={"invitation_id": invitation_id})
|
|
295
|
+
return _resp_json(resp)
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
@mcp.tool()
|
|
299
|
+
async def participants_list(meeting_id: str) -> str:
|
|
300
|
+
"""List participants of a meeting."""
|
|
301
|
+
client = await _get_client()
|
|
302
|
+
resp = await client.get(f"{ZOOM_API_URL}/api/v1/participants.list", params={"meeting_id": meeting_id})
|
|
303
|
+
return _resp_json(resp)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
@mcp.tool()
|
|
307
|
+
async def participants_admit(meeting_id: str, user_email: str) -> str:
|
|
308
|
+
"""Admit a user into a meeting (host action)."""
|
|
309
|
+
client = await _get_client()
|
|
310
|
+
resp = await client.post(f"{ZOOM_API_URL}/api/v1/participants.admit", json={"meeting_id": meeting_id, "user_email": user_email})
|
|
311
|
+
return _resp_json(resp)
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
@mcp.tool()
|
|
315
|
+
async def participants_remove(meeting_id: str, user_email: str) -> str:
|
|
316
|
+
"""Remove a user from a meeting (host action)."""
|
|
317
|
+
client = await _get_client()
|
|
318
|
+
resp = await client.post(f"{ZOOM_API_URL}/api/v1/participants.remove", json={"meeting_id": meeting_id, "user_email": user_email})
|
|
319
|
+
return _resp_json(resp)
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
@mcp.tool()
|
|
323
|
+
async def participants_set_role(meeting_id: str, user_email: str, role: str) -> str:
|
|
324
|
+
"""Set role for a participant (e.g., host, co-host)."""
|
|
325
|
+
client = await _get_client()
|
|
326
|
+
resp = await client.post(f"{ZOOM_API_URL}/api/v1/participants.set_role", json={"meeting_id": meeting_id, "user_email": user_email, "role": role})
|
|
327
|
+
return _resp_json(resp)
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
# Controls / Chat / Notes / Transcript
|
|
331
|
+
@mcp.tool()
|
|
332
|
+
async def meetings_recording_start(meeting_id: str) -> str:
|
|
333
|
+
"""Start recording a meeting (host action)."""
|
|
334
|
+
client = await _get_client()
|
|
335
|
+
resp = await client.post(f"{ZOOM_API_URL}/api/v1/meetings.recording.start", json={"meeting_id": meeting_id})
|
|
336
|
+
return _resp_json(resp)
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
@mcp.tool()
|
|
340
|
+
async def meetings_recording_stop(meeting_id: str) -> str:
|
|
341
|
+
"""Stop recording a meeting (host action)."""
|
|
342
|
+
client = await _get_client()
|
|
343
|
+
resp = await client.post(f"{ZOOM_API_URL}/api/v1/meetings.recording.stop", json={"meeting_id": meeting_id})
|
|
344
|
+
return _resp_json(resp)
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
@mcp.tool()
|
|
348
|
+
async def chat_post_message(meeting_id: str, content: str) -> str:
|
|
349
|
+
"""Post a chat message in a meeting."""
|
|
350
|
+
client = await _get_client()
|
|
351
|
+
resp = await client.post(f"{ZOOM_API_URL}/api/v1/chat.post_message", json={"meeting_id": meeting_id, "content": content})
|
|
352
|
+
return _resp_json(resp)
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
@mcp.tool()
|
|
356
|
+
async def chat_list(meeting_id: str) -> str:
|
|
357
|
+
"""List chat messages in a meeting."""
|
|
358
|
+
client = await _get_client()
|
|
359
|
+
resp = await client.get(f"{ZOOM_API_URL}/api/v1/chat.list", params={"meeting_id": meeting_id})
|
|
360
|
+
return _resp_json(resp)
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
@mcp.tool()
|
|
364
|
+
async def transcripts_get(meeting_id: str) -> str:
|
|
365
|
+
"""Get meeting transcripts (if available)."""
|
|
366
|
+
client = await _get_client()
|
|
367
|
+
resp = await client.get(f"{ZOOM_API_URL}/api/v1/transcripts.get", params={"meeting_id": meeting_id})
|
|
368
|
+
return _resp_json(resp)
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
@mcp.tool()
|
|
372
|
+
async def transcripts_create(meeting_id: str, start: float, end: float, content: str, speaker: Optional[str] = None) -> str:
|
|
373
|
+
"""Create a transcript segment (sandbox helper)."""
|
|
374
|
+
client = await _get_client()
|
|
375
|
+
body = {"meeting_id": meeting_id, "start": start, "end": end, "content": content}
|
|
376
|
+
if speaker is not None:
|
|
377
|
+
body["speaker"] = speaker
|
|
378
|
+
resp = await client.post(f"{ZOOM_API_URL}/api/v1/transcripts.create", json=body)
|
|
379
|
+
return _resp_json(resp)
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
@mcp.tool()
|
|
383
|
+
async def transcripts_list(meeting_id: str) -> str:
|
|
384
|
+
"""List transcript segments of a meeting."""
|
|
385
|
+
client = await _get_client()
|
|
386
|
+
resp = await client.get(f"{ZOOM_API_URL}/api/v1/transcripts.list", params={"meeting_id": meeting_id})
|
|
387
|
+
return _resp_json(resp)
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
# Profile
|
|
391
|
+
@mcp.tool()
|
|
392
|
+
async def zoom_get_me() -> str:
|
|
393
|
+
"""Get current user profile."""
|
|
394
|
+
client = await _get_client()
|
|
395
|
+
resp = await client.get(f"{ZOOM_API_URL}/api/v1/me")
|
|
396
|
+
return _resp_json(resp)
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
@mcp.tool()
|
|
400
|
+
async def notes_create(meeting_id: str, content: str) -> str:
|
|
401
|
+
"""Create a note for a meeting."""
|
|
402
|
+
client = await _get_client()
|
|
403
|
+
resp = await client.post(f"{ZOOM_API_URL}/api/v1/notes.create", json={"meeting_id": meeting_id, "content": content})
|
|
404
|
+
return _resp_json(resp)
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
@mcp.tool()
|
|
408
|
+
async def notes_list(meeting_id: str) -> str:
|
|
409
|
+
"""List notes of a meeting."""
|
|
410
|
+
client = await _get_client()
|
|
411
|
+
resp = await client.get(f"{ZOOM_API_URL}/api/v1/notes.list", params={"meeting_id": meeting_id})
|
|
412
|
+
return _resp_json(resp)
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
@mcp.tool()
|
|
416
|
+
async def notes_get(note_id: str) -> str:
|
|
417
|
+
"""Get a note by id."""
|
|
418
|
+
client = await _get_client()
|
|
419
|
+
resp = await client.get(f"{ZOOM_API_URL}/api/v1/notes.get", params={"note_id": note_id})
|
|
420
|
+
return _resp_json(resp)
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
@mcp.tool()
|
|
424
|
+
async def notes_update(note_id: str, content: str) -> str:
|
|
425
|
+
"""Update a note's content."""
|
|
426
|
+
client = await _get_client()
|
|
427
|
+
resp = await client.post(f"{ZOOM_API_URL}/api/v1/notes.update", json={"note_id": note_id, "content": content})
|
|
428
|
+
return _resp_json(resp)
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
@mcp.tool()
|
|
432
|
+
async def notes_delete(note_id: str) -> str:
|
|
433
|
+
"""Delete a note by id."""
|
|
434
|
+
client = await _get_client()
|
|
435
|
+
resp = await client.post(f"{ZOOM_API_URL}/api/v1/notes.delete", json={"note_id": note_id})
|
|
436
|
+
return _resp_json(resp)
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
def _resp_json(resp: httpx.Response) -> str:
|
|
440
|
+
if resp.status_code < 200 or resp.status_code >= 300:
|
|
441
|
+
try:
|
|
442
|
+
return json.dumps({"error": f"HTTP {resp.status_code}: {resp.text}"})
|
|
443
|
+
except Exception:
|
|
444
|
+
return json.dumps({"error": f"HTTP {resp.status_code}"})
|
|
445
|
+
try:
|
|
446
|
+
return json.dumps(resp.json())
|
|
447
|
+
except Exception:
|
|
448
|
+
return json.dumps({"ok": True})
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
def main() -> None:
|
|
452
|
+
print("Starting Zoom MCP Server (Sandbox)...", file=sys.stderr)
|
|
453
|
+
sys.stderr.flush()
|
|
454
|
+
host = os.getenv("ZOOM_MCP_HOST", "localhost")
|
|
455
|
+
env_port = os.getenv("PORT", "").strip()
|
|
456
|
+
if env_port.isdigit():
|
|
457
|
+
port = int(env_port)
|
|
458
|
+
else:
|
|
459
|
+
port = _port_from_registry(8851)
|
|
460
|
+
mcp.run(transport="http", host=host, port=port)
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
if __name__ == "__main__":
|
|
464
|
+
main()
|
|
465
|
+
|
|
466
|
+
|
dt_arena/src/__init__.py
ADDED
|
File without changes
|
|
File without changes
|