https://linen.dev logo
Composio MCP tools not being exposed to OpenClaw a...
# 🖥│support-forum
h
Beginning last weekend, my OpenClaw agent is suddenly not able to instantiate any tools (specifically Gmail and Dropbox) even though the Composio Dashboard shows that they are active. Discovery: When calling tool_search (the Gateway's native way of listing MCP capabilities), the server returns general orchestration tools (e.g., COMPOSIO_SEARCH_TOOLS, COMPOSIO_REMOTE_WORKBENCH). Execution: When actually attempting to invoke those same tools, the system returns a Tool not found error. The MCP server is successfully connecting via API key, and the Gateway is receiving the initial tool list (capabilities). However, the individual tool definitions are not being registered in the agent's active session. This results in a loop where tools are visible in the search catalog but return 'Not Found' upon invocation. This is happening across multiple app toolkits (Gmail and Dropbox). Anybody seen this before and know how to fix this?
o
I have'nt seen that one before
Do you have the Composio CLI installed on the machine where OpenClaw is running?
h
I seem to be closer to getting this working again... Upgraded OpenClaw to v2026.9.8 and then installed the plugin and the CLI and it's working most of the time now. Still having an issue with using the S3 bucket for staging files, but at least it's able to see and pull down e-mail from Gmail and files from Dropbox.
c
Hi, Thanks for the update. It’s good to hear OpenClaw can now read Gmail and Dropbox most of the time. Composio normally exposes discovery and execution tools first, so seeing those in the initial MCP tool list does not mean every Gmail or Dropbox action will appear there as a separate tool. Your upgrade and setup changes coincided with the improvement, but we can’t identify which change resolved the earlier “Tool not found” errors. We can’t yet confirm what is failing in the S3 staging step. Please share the exact error text shown in OpenClaw, without any credentials or tokens. If the failed action has an entry under Platform → Logs, please share its Log ID too. Those details will identify whether the failure reached a Composio tool execution and where the staging flow stopped.
h
The agent passes the URL by embedding it directly into a Python script that it then sends to the COMPOSIO_REMOTE_WORKBENCH tool for execution. Here is the exact process it follows: 1. Capture: Agent runs composio execute DROPBOX_READ_FILE via the shell. 2. Extract: Agent pulls the s3url string from the JSON response. 3. Inject: Agent writes a Python snippet using the requests library: ``` import requests s3_url = "https://temp.4d4f... [the full URL]" response = requests.get(s3_url) content = response.text 4. Execute: Agent wraps this code in a tool_call to composio__COMPOSIO_REMOTE_WORKBENCH under the code_to_execute parameter. The Point of Failure: When the workbench attempts to run requests.get(s3_url), it returns a 400 Bad Request. One detail that might be relevant: because the S3 URL is very long and contains many special characters (including & and =), there is a possibility that the URL is being slightly altered or improperly escaped when it travels from the Agent's context into the workbench's execution environment, which would immediately invalidate the S3 signature and cause a 400 error.
c
Hi, Thanks for the detailed breakdown — that hypothesis is very likely correct. When an agent re-types a long pre-signed URL into generated code, it frequently gets truncated or subtly altered (a dropped
&
parameter is enough to invalidate the signature and produce exactly the 400 you're seeing). The same applies if the URL sits in context for a while before use, since pre-signed URLs expire. The reliable fix is to not route the URL through the agent's context at all.
COMPOSIO_REMOTE_WORKBENCH
runs in a sandbox that has helper functions preloaded, including
run_composio_tool
. Have the agent generate code that does both steps in one cell:
Copy code
result = run_composio_tool("DROPBOX_READ_FILE", {"path": "/your/file.txt"})
s3_url = result["data"]["s3url"]  # adjust key to the actual response shape

import requests
content = requests.get(s3_url).text
The URL then travels machine-to-machine — fetched fresh and used immediately — with no LLM re-serialization in between. As a bonus, this also keeps the file content out of the agent's context window. If you'd like to confirm the diagnosis first, the S3 error body would settle it: S3 returns an XML payload with the 400 (e.g. a missing/invalid query parameter), which distinguishes a mangled URL from an expired one. A quick
print(len(s3_url))
in the workbench compared against the original response length works too.
h
This was so helpful. Thank you for this detailed fix -- once I directed my agent to use this approach, everything associated with Dropbox downloads and updates worked flawlessly.