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