cloudflare / cloudflare/agents
MCP tool invocations (getAITools-merged) emit no observability event — tool calls are invisible to the SDK event stream
- Dominant language
- TypeScript
- Stars
- 5.6k
- Forks
- 711
- Avg merge
- 1d 20h
- Merged PRs (30d)
- 53
Description
## Summary
When MCP server tools are merged into a Think turn via `this.mcp.getAITools()`, an actual **tool invocation** emits no observability signal. The SDK's structured event stream (rpc / chat / tool / schedule / fiber / error) and Think's own events cover the model turn, but a call to an MCP-merged tool's `execute` produces no event for *which* MCP tool ran, its arguments/outcome, latency, or error. An operator cannot tell "the model called `generate_image` and the server erred" from "the model narrated calling it but never did."
Observed in `agents@0.17.1` + `@cloudflare/think@0.11.1`.
## Detail
`getAITools()` builds each tool's `execute` as a thin wrapper over `callTool({ name, serverId, arguments })`:
```js
const toolKey = `tool_${tool.serverId.replace(/-/g, "")}_${tool.name}`;
entries.push([toolKey, { description, title, execute: async (args) => {
const result = await this.callTool({ arguments: args, name: tool.name, serverId: tool.serverId });
if (result.isError) { ... throw new Error(message); }
...
}}]);
```
There's no `observability.emit(...)` around this path, and Think spreads the result straight into the turn's toolset (`...this.mcp?.getAITools?.() ?? {}`). So unlike a native tool (which surfaces via the SDK `tool` event → your observability sink), an MCP tool call is invisible: no name, no serverId, no duration, no error surfaced as an event. The only trace is whatever the model chooses to say in its text.
## Impact
MCP tool calls — often the highest-value, most failure-prone hop (external server, per-user auth, rate limits) — are the least observable part of the turn. Debugging "the agent said it generated an image but nothing happened" requires guessing, because there's no ground-truth signal that the tool was even invoked.
## Ask
Emit an SDK observability event when an MCP-merged tool's `execute` runs (start + result/error), mirroring the native `tool` event — at minimum `{ serverId, toolName, ok, durationMs, error? }` (never args/results by default; they carry user content). That lets existing observability bridges treat MCP tool calls like any other tool call.
## Workaround
Wrap the `getAITools()`-merged tools before the turn (e.g. via a `beforeTurn` `TurnConfig.tools` override) and instrument `execute` yourself — but this depends on the private `tool__` key shape, which is a fragile hook.
Contributor guide
Assessment
This issue has not been assessed yet.