Skip to content

docs(workflows): say what to check when MCP tools don't load - #23

Open
hercemer42 wants to merge 2 commits into
mainfrom
feature/prd-876-document-mcp-tool-load-failures
Open

docs(workflows): say what to check when MCP tools don't load#23
hercemer42 wants to merge 2 commits into
mainfrom
feature/prd-876-document-mcp-tool-load-failures

Conversation

@hercemer42

@hercemer42 hercemer42 commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Follow-up to PRD-876. The executor half (agent-nodejs#1806) makes Forest Runtime name why an MCP connector failed to load its tools; this documents what a reader does with that.

Forest Runtime → Observability

New subsection, "When an MCP step can't load its tools": the Error line as it appears at the default log level, and what each failure kind implies for the fix — auth reconnect the credential, connection unreachable or past the 15s per-server timeout, unknown read the error text. Also states that a server answering with no tools is an empty list rather than an error, which is the behaviour the PR changes.

The LOG_LEVEL row in Tuning now says what Debug buys you (per-server tool count and load time), since "Debug, Info, Warn, or Error" gave no reason to ever lower it.

MCP Connectors

New "A connector's tools don't load" section. The page previously ended at "Check that all the expected tools are exposed" with no guidance when they aren't. It now names the three failure modes in the order worth checking — nothing published, credential rejected, server unreachable — and points self-hosters at the runtime logs that distinguish them.

Nothing removed; no existing statement was inaccurate.

Note

Document MCP tool loading failures and empty tool list behavior in workflow docs

  • Adds a new subsection in forest-runtime.mdx explaining what to check when an MCP step can't load its tools, including a JSON log example and a breakdown of kind values (auth, connection, unknown).
  • Clarifies that a server returning no tools is not a connection failure — it results in an empty tool list and is a server configuration issue.
  • Notes in mcp-servers.mdx that an empty tools list in step 4 means the server responded but published no tools.
  • Documents that setting LOG_LEVEL to Debug adds one line per MCP server with its tool count and load time.

Macroscope summarized d4a9ab2.

An empty tool list, a rejected credential and an unreachable server are the
three ways a connector fails, and they need three different fixes. Forest
Runtime now names which one it was in its logs, so point readers at that
rather than leaving "check that all the expected tools are exposed" as the
only guidance.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@linear-code

linear-code Bot commented Aug 7, 2026

Copy link
Copy Markdown

PRD-876

@mintlify

mintlify Bot commented Aug 7, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
forest 🟢 Ready View Preview Aug 7, 2026, 7:27 AM

💡 Tip: Enable Workflows to automatically generate PRs for you.

Forest Runtime logs the reason at `Error`, so it is in your logs without changing `LOG_LEVEL`:

```json
{

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium workflows/forest-runtime.mdx:247

The documented JSON example shows a failures array with server, kind, and error fields, but the actual production log emits requestedMcpServerId, mcpServerName, and failedConfigNames — not failures. The kind field does not exist in the log, so operators following the guidance to use kind to distinguish auth, connection, and unknown failures cannot find it. Update the example to match the real payload, or change the logging to include the kind field.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @product/process/workflows/forest-runtime.mdx around line 247:

The documented JSON example shows a `failures` array with `server`, `kind`, and `error` fields, but the actual production log emits `requestedMcpServerId`, `mcpServerName`, and `failedConfigNames` — not `failures`. The `kind` field does not exist in the log, so operators following the guidance to use `kind` to distinguish `auth`, `connection`, and `unknown` failures cannot find it. Update the example to match the real payload, or change the logging to include the `kind` field.

A symptom-titled troubleshooting section restated the runtime page's failure
taxonomy in product language, so the same knowledge lived on two pages and
would drift. Only the empty-list meaning was new, and it belongs where the
reader meets it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@PMerlet PMerlet left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adversarial pass: I checked every claim against the executor code (agent-nodejs#1806 head) and ai-proxy on main. The mechanics check out — kind values, the 15s per-server timeout (LOAD_TOOLS_TIMEOUT_MS), the Debug per-server line, Error-at-default-level, and the empty-list behavior all match the code. forestadmin#9892 validates the UI claim in mcp-servers.mdx (empty list → no banner, 503 → banner).

One PR-level issue: the description promises a section that isn't in the diff. It announces a new "A connector's tools don't load" section in mcp-servers.mdx naming the three failure modes and pointing self-hosters at the runtime logs — but the actual diff on that file is a single sentence (+1/−1). Either the section was dropped without updating the description, or a commit is missing. Worth reconciling before merge, since the cross-page story the description tells doesn't exist.

Inline comments cover the rest — the only real content issue is the auth bullet vs the OAuth auto-refresh flow.


The Docker image ships with [OpenTelemetry](https://opentelemetry.io/) APM built in, compatible with any OTLP backend (Datadog, Grafana Tempo, Jaeger, Honeycomb…). It is **off by default** and turns on as soon as you set `OTEL_EXPORTER_OTLP_ENDPOINT`. OpenTelemetry is bundled only in the Docker image, not the npm package.

### When an MCP step can't load its tools

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Terminology nit: this page (and the workflows overview) calls the step type "MCP Tasks" — see line 212, "guidance, decisions, MCP Tasks, and AI-assisted data steps". Suggest "When an MCP Task can't load its tools" for consistency.


### When an MCP step can't load its tools

Forest Runtime logs the reason at `Error`, so it is in your logs without changing `LOG_LEVEL`:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sequencing caveat: merged ≠ released. This doc goes live when this PR merges, but the failures shape only reaches a self-hoster once agent-nodejs#1806 ships in a released @forestadmin/workflow-executor / Docker image and they upgrade. On earlier images the same event logs failedConfigNames instead, and an empty tool list from a healthy server is flagged as a failure (the old inference). If the merge→release gap is short, fine — otherwise consider a minimum-version note here.

{
"level": "Error",
"message": "MCP servers failed to load tools",
"mcpServerName": "acme-crm",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This example is trimmed compared to what the logger actually emits: the console logger adds "timestamp" and this event also carries "requestedMcpServerId" (the executor README's own example keeps it). Since the intro sells this as the line "as it appears", either show the full shape or say fields were elided.

Also worth a clause: the JSON shape is the non-TTY output (the Docker default). A TTY run gets the pretty logger's HH:MM:SS error … format instead.


`kind` tells you where to look:

- `auth` — the server rejected the credential. Reconnect the connector, or renew its token.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This bullet doesn't survive contact with the OAuth path in remote-tool-fetcher.ts:

  • On an OAuth connector, a 401 triggers an automatic token refresh + one retry. If the retry succeeds, the executor logs Info "MCP tools loaded after refreshing the credential" and no action is needed — as written, this bullet sends the reader to reconnect a connector that already self-healed.
  • If the retry also fails on auth, the code throws OAuthReauthRequiredError before errorOnPartialLoadFailure runs — so a persistent OAuth 401 never appears as kind: "auth" in this log line. The 401 surfaces as ai-proxy's bridged line (Error loading tools for <name>) instead.

Net effect: kind: "auth" here effectively concerns static-credential connectors (apiKey/header). Suggest mentioning the auto-refresh and the recovery Info line, e.g.: "auth — the server rejected the credential. For OAuth connectors the runtime refreshes the token and retries once on its own — act only if the failure repeats without a follow-up MCP tools loaded after refreshing the credential line. For static credentials, renew the credential in the connector's configuration."


- `auth` — the server rejected the credential. Reconnect the connector, or renew its token.
- `connection` — unreachable, refused, or slower than the 15-second per-server load timeout.
- `unknown` — the server answered but the load failed anyway; `error` carries the reason.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One nugget worth adding here: classifyMcpLoadError only treats 401 as auth — a 403 (credential valid but insufficient scope/permission) is deliberately classified unknown, since no token refresh can fix it. A reader whose server rejects the credential with 403 will see unknown while the auth bullet above seems to cover their case. Suggest: "unknown — the server answered but the load failed anyway (including 403 permission/scope errors); error carries the reason."

}
```
4. Check that all the expected tools are exposed.
4. Check that all the expected tools are exposed. An empty list means the server answered but published nothing — a configuration problem on the server, not a connection failure.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True on the executor path once forestadmin#9892 lands (its manual-verification table covers exactly these three cases). One caveat from that PR: the browser-engine path keeps the empty-means-failed inference — it can't distinguish "no tools" from "this server failed", so a project listing tools through that path still shows the "could not be retrieved" warning for a healthy server publishing no tools. If readers of this page can be on that path, this sentence overpromises; if the setup flow always goes through the executor, ignore me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants