cloudflare / cloudflare/agents

MCP tool invocations (getAITools-merged) emit no observability event — tool calls are invisible to the SDK event stream

Open
#1,857 0 comments 0 reactions 1 assignee Claimed by @mattzcarey View on GitHub
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.