feat: Add report-problem tool for agents to report issues#1050
feat: Add report-problem tool for agents to report issues#1050RobertCrupa wants to merge 10 commits into
Conversation
jirispilka
left a comment
There was a problem hiding this comment.
Thanks Robert, the architecture is right: dedicated tool, dedicated Segment event. Before merge we need to discuss it a bit more Summary first, details below.
With Claude
To discuss
- Rename
share-feedbacktoreport-problem. The tool only gets called at failure moments, so it is a problem-report channel — name it that. This also makes it plausibly compatible with the Anthropic directory review: a descriptive diagnostic tool, no MUST instructions, only a gentle nudge. - Drop
npsRating. Failure-conditioned sampling makes the score junk data, and a rating field makes no sense on a problem-report tool. - Solicitation: remove MUST, keep one gentle nudge. Scope the nudge so it never fires on payment/approval responses or the agent's own input errors.
- Move client gating out of
upsertTools. It has real bypasses there. Client-based filtering belongs in the same compose step that already resolves the tool set per client (initialize handler), behind a generic tool→client blocklist, not an Anthropic-specific function. - Confirm with Anthropic instead of guessing. Keep the block on Claude surfaces in v1, email with the exact design, remove the block when they OK it.
- Smaller fixes: don't serve the tool when telemetry is off, cap input sizes, per-session submission cap, update docs, coordinate the internal fan-out.
Product
npsRating is wrong. Every discovery mechanism (nudge, footers, instructions) triggers on failure. Nothing prompts a rating when things work. So the score samples almost only broken sessions — it measures "how annoyed are agents when things break", not Actor quality. LLM ratings also cluster in a narrow band, so NPS math is meaningless even with balanced sampling. Drop the field. For Actor quality, use run telemetry we already have (success rate, failure categories, call-actor → get-dataset-items completion) — unbiased, covers every session, can't be gamed by prompt wording.
Rename to report-problem. One tool, one honest job: "Report a problem with an Apify tool or Actor: what you were doing, what failed." This matches what the sampling can actually deliver, and it changes the Anthropic story. Their rejection criteria target vendor-serving behavioral instructions ("call this before giving up", "you MUST"), not diagnostic tools with descriptive wording — our existing nextStep guidance is fine for the same reason: it serves the user's task. A neutral report-problem tool with a gentle nudge is a defensible submission. Note: positive feedback / Actor ratings are explicitly out of scope for v1; if we ever want them, that's a separate design with success-moment sampling.
Other product points:
- Privacy is currently prompt-only ("do not include personal data"). The fan-out side (internal) must treat report text as untrusted input — escape markdown, no auto-actions — because this is an unauthenticated free-text write path.
- Document the tool and how to disable it (
tools=without the category) in README.
Code
-
Client gating is in the wrong layer and has bypasses. The
upsertToolsfilter readsinitializeRequestData, which is only set duringinitialize. With explicit--ui default|appsorUI_MODE, stdio composes tools before initialize and the block never applies. The footers are static constants in three tool descriptions, so Claude surfaces see "call share-feedback before giving up" regardless — the exact pattern in the exact place the directory reviews. And internal's multi-node recovery restores tools on a node that never saw initialize.Fix: make "client known" (not "mode resolved") the trigger for composing helper tools — the pending-queue mechanism already exists, the condition is just wrong for explicit modes. MCP guarantees no tools/list before initialize, so deferring composition is always safe. Then apply a generic policy inside that one compose step:
const TOOL_CLIENT_BLOCKLIST: Record<string, string[]> = { [HELPER_TOOLS.PROBLEM_REPORT]: ['claude', 'anthropic'], };
Same step decides the footer variants of the three tool descriptions (append only when report-problem is served). upsertTools stays policy-free. Blocklist is data, not code, because we'll extend it: OpenAI's directory has near-identical criteria, and localAgent (seen in our telemetry, matches neither substring) may need adding — verify in Mixpanel first whether it's a Claude surface or an API/Agent-SDK path we actually want feedback from. Longer term, put a vendor field in mcp-client-capabilities (its registry only knows claude-ai and claude-code today) and match exactly instead of by substring.
-
Scope the nudge. It currently fires on every isError: true result, including payment-required and approval responses — telling the agent to "report to Apify" when the correct next step is the payment flow — and on the agent's own invalid-input errors. Exclude those categories, and gate it with the same predicate as the tool itself.
-
Instructions: replace MUST with one line. "If a tool or Actor fails and you cannot resolve it, you can report it with report-problem." The MUST version delays failure reports to users and invites junk submissions.
-
Don't serve the tool when telemetry is disabled. With --telemetry-enabled false it's still listed and returns "Feedback submitted" while the event goes nowhere. The tool has no other function.
-
Cap inputs. message max ~2000 chars, relatedTools max ~20 items, "keep it to a few sentences" in the description.
-
Per-session submission cap (in-memory is fine — it's telemetry, not critical data). Return "already recorded" past the cap.
-
Rename mechanics. Decide the Segment event name now (MCP Problem Report vs keeping MCP Agent Feedback) — renaming events later fragments Mixpanel history. The feedback category name can stay.
|
@jirispilka I think it was JC's proposal to make this |
MQ37
left a comment
There was a problem hiding this comment.
I agree with Jirka, let's make sure it is disabled when telemetry is off.
Also I noticed that the long running task path may be missing the report feedback nudge - but I would honestly remove the nudge and just state that there is this tool available, but not tell agent must/should use it, just state the fact that the tool is available.
Also I agree that we should focus on the negative cases and I would rename the tool to report-issue and remove the score and just keep message: string as an input to keep it simple.
| // Tools are final here (updateToolsAfterServerModeResolved ran above, after the | ||
| // Anthropic hard-block applied), so tool presence is the ground truth for whether to | ||
| // advertise share-feedback in the instructions. | ||
| result.instructions = getServerInstructions(this.serverMode, this.tools.has(HELPER_TOOLS.FEEDBACK_SHARE)); |
There was a problem hiding this comment.
nit: what about we pass server instance instead and the getServerInstructions can then check whatever it wants without adding additional arg?
|
Fair enough, I think it makes sense to have this to report problems/issues only, and drop NPS in that case. we'll see how it works later |
|
BTW, once we have this, we should gently nudge the agent to use this tool on problems. Most tools are not loaded into context, so otherwise the agents might not know about it at all |
|
@RobertCrupa Follow-up review found one edge-case bug in
|
|
@RobertCrupa Fix for the cross-node `report-problem` gating bug (comment above) is ready: apify/apify-mcp-server-internal#641. Root cause was in `apify-mcp-server-internal`, not here — `handleExistingSession`'s orphaned-session fallback (`streamable.ts`) rewrote Redis session state with tools only, dropping `clientInfo` whenever a node still held the session in memory but Redis had evicted it. A later restore on a different node then read `clientInfo: null` and could never recover it, silently disabling any client-gated tool for the rest of that session. The internal PR includes a real two-node regression test (connect → confirm `report-problem` listed → delete session from Redis → hit the same node again → hit a second node that never saw the session → confirm still listed). It's flagged do-not-merge-yet: it depends on this PR merging and releasing, then `apify-mcp-server-internal`'s `@apify/actors-mcp-server` dependency bumping past `0.11.5` to pick up `report-problem`. Once that lands, apify-mcp-server-internal#641 is ready to go in right behind it. |
MQ37
left a comment
There was a problem hiding this comment.
Cool, thank you! Pre-approving.
But please before merging please let's nudge/inform the agent about the report tool existence also in the INVALID INPUT tool failure as there can be some funky bugs causing input issues that should be reported and agent might not report that - if we keep getting lots of spam, we can then rework the nudging.
Adds the report-problem tool for reporting broken/missing MCP tools and Actors to the Apify team. Includes client-blocklist gating, a server-instructions nudge, an error nudge on failed tool calls, and an 'MCP Agent Feedback' telemetry event.
4fdf542 to
79371c9
Compare
jirispilka
left a comment
There was a problem hiding this comment.
Thanks, looks better now. Although there are multiple issues that we should fix.
Please also the README with report-problem tool
Before merging, I would love to see that we receive the event is segment. If you don't have access yet, lets ask @mr-rajce
And we need to decide on the category to which this tool will belong.
Further, claude complains here:
server.ts — payment responses are nudged inconsistently, and the comment at 1884–1886 is inaccurate.
A 402/x402 payment error is classified INVALID_INPUT (:1328, :1796), which is not in NON_NUDGE_FAILURE_CATEGORIES (that's only AUTH + PERMISSION_APPROVAL_REQUIRED), so it receives the softer nudge — not suppression. Two consequences:
The sync path (:1325 → captureResult → nudge) appends the softer nudge to a paywall response, while the task path (:1773) returns at :1802 before any nudge site — so the same 402 behaves differently by execution mode. The comment at :1884 claims the paths "behave identically"; they don't.
That same comment says INVALID_INPUT is self-suppressed and "only real defects get it" — both false.
Suggest: decide whether payment should be nudged (I'd say no — it's a billing state, not a defect), make both paths match, and fix the comment. Note you can't blanket-suppress INVALID_INPUT (genuine input bugs should still get the softer nudge), so gate payment via its 402 status or by isolating the payment/approval branches from the nudge.
Jiri's review (#1050): report-problem should not have its own category. Move it into the dev category and drop the dedicated report-problem category. To preserve default-on (agents must see the tool or they won't report), inject it into the default (no-selector) tool set in getToolsForServerMode; server-side servability gating still applies. An explicit tools= list still excludes it, and tools=dev selects it. Also simplify the compose filter per review. Claude-Session: https://claude.ai/code/session_01TV4x9VgMCvXGF7TftskLAn
Payment is a billing state, not a defect, so a 402 must not trigger a report-problem nudge. A 402 is classified INVALID_INPUT, so gate the nudge on the 402 HTTP status rather than blanket-suppressing INVALID_INPUT (genuine input bugs still get the softer nudge). The sync path previously appended the softer nudge to paywalls while the task path did not; both now suppress it. Extract the thrice-repeated nudge into withReportProblemNudge and fix the inaccurate task-path comment that claimed the paths behaved identically. Refs #1050. Claude-Session: https://claude.ai/code/session_01TV4x9VgMCvXGF7TftskLAn
Per review (#1050): rename the Segment event from "MCP Agent Feedback" to "MCP Reported Problem" (deciding the name now avoids fragmenting Mixpanel history later), and rename the feature's identifiers to match (trackReportedProblem, buildReportedProblemProperties, ReportedProblemTelemetryProperties). Claude-Session: https://claude.ai/code/session_01TV4x9VgMCvXGF7TftskLAn
readOnlyHint: true misrepresents a tool that forwards a submission to the Apify team; a client or a directory review comparing the description to the annotation would flag it. Set it to false. Refs #1050. Claude-Session: https://claude.ai/code/session_01TV4x9VgMCvXGF7TftskLAn
local-agent-mode-apify is an Anthropic surface; hide report-problem from it pending the directory review, alongside the existing claude/anthropic substrings. Refs #1050. Claude-Session: https://claude.ai/code/session_01TV4x9VgMCvXGF7TftskLAn
Add report-problem to the tools table with a footnote for its telemetry and client gating, and note its dev-category placement and how to disable it. Refs #1050. Claude-Session: https://claude.ai/code/session_01TV4x9VgMCvXGF7TftskLAn
It depends on how you run it, it might be in the dev env only? |

Implements the
report-problemMCP tool from #748 — a channel for agents to report problems with Apify's tools or Actors, so we get signal on where agents get stuck.What's included
report-problem: returns a plain-text acknowledgement, always success.readOnlyHint: true, not payment-gated.Schema:
message— required string, 1–2000 chars. What happened.actorId— optional string, ≤200 chars.actorRunId— optional string, ≤200 chars.relatedTools— optional string[], up to 20 items, each ≤100 chars.report-problemtool category in the registry.MCP Agent FeedbackSegment event (fields mapped to snake_case, absent optionals dropped) alongside the standard tool-call event. Identity handling mirrors the tool-call event.Gating — served only when all three hold
claude,anthropic) matched againstclientInfo.name. The tool cannot be judged until the client is known, so it is withheld until initialize and added on the flush if the client allows.report-problemcategory is enabled (default on).All other tools are unconditionally servable, so recovery loads compose them eagerly and they survive a load that never sees an initialize; only
report-problemis withheld until the client is known.Discovery
isError) tool results get a report-problem nudge appended to their text, prompting the agent to report at the moment it decides what to do next. Full nudge for internal/unknown errors; a softer nudge forINVALID_INPUT(likely the agent's own mistake); suppressed for expected user-resolvable states (auth, payment/approval required). Also applied to failed long-running task results. Never appended when the tool isn't served or whenreport-problemitself is the failing tool.report-problem, emitted only when the tool is actually served.Testing
report-problem-on-tool-error: injects acall-actorfailure and checks the agent proactively reports the blocker (rather than only telling the user it failed). Required adding tool-failure injection to the eval harness.report-problemlisting and gating.claude-family client while remaining available to non-Anthropic clients).TODO
Closes #748