feat(agent-toolkit): fix InvalidFormToken failures in form tools - #480
Open
stasshw wants to merge 1 commit into
Open
feat(agent-toolkit): fix InvalidFormToken failures in form tools#480stasshw wants to merge 1 commit into
stasshw wants to merge 1 commit into
Conversation
InvalidFormToken is the largest single get_form error bucket in production
(1,285 calls over two weeks) and it hits every MCP client, not one of them.
Two root causes, both fixed here.
1. Agents pass a form URL instead of the token. All four form tools now
normalize the input locally via resolveFormToken: a full
forms.monday.com/forms/<token> URL is parsed, a shortened wkf.ms link is
resolved by following the redirect, and anything that still fails
/^[a-f0-9]{32}$/i is rejected before the API call with a message that
restates what a valid token is. Roughly 31% of observed InvalidFormToken
calls carried a recoverable URL.
2. Agents had no way to discover a token in the first place, so they guessed
board ids and view ids. get_board_info now returns view_specific_data as
real JSON (parsed from view_specific_data_str, which the platform only
populates for form views, and omitted entirely when empty), and accepts
filters.views.type so a caller can ask for {"type": "FormBoardView"} and
reach the token from a board id alone, with no view id needed. The form
tools point at that call as a [REQUIRED PRECONDITION].
Also fixes a circular instruction in update_form, which told agents to call
get_form "to resolve the formToken" — a call that requires the token as input.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
InvalidFormTokenis the largest singleget_formerror bucket in production — 1,285 calls over Aug 24 – Sep 6 — and it is not specific to one client. Per-client error rates onget_form:InvalidFormTokenInspecting what agents actually sent reveals two distinct root causes.
Cause 1 — agents pass a URL, not the token
Of the 1,285 bad values: 858 bare strings, 353 shortened
wkf.mslinks, 51 full form URLs, 23 other. The workforms service enforces/^[a-f0-9]{32}$/ibefore any lookup, so a URL is rejected outright even though the token is sitting inside it.All four form tools (
get_form,update_form,create_form_submission,form_questions_editor) now normalize the input locally through a sharedresolveFormToken:forms.monday.com/forms/<token>?r=use1URL is parsedwkf.mslink is resolved by following the redirectThat recovers roughly 31% of observed
InvalidFormTokencalls (404 of 1,285) which previously round-tripped to a hard failure.Cause 2 — agents had no way to obtain a token
The only field carrying a form token is
BoardView.view_specific_data_str, whichget_board_infodid not request. With no discovery path, agents guessed — passing board ids, view ids, and item ids asformToken.get_board_infonow returnsview_specific_dataas real JSON, parsed from the stringified field. The platform populates it only for form views ({token, disabled, region}, ~70 bytes); every other view type returns"{}", which is omitted entirely, so non-form boards pay nothing.get_board_infoacceptsfilters.views.type, wired to the already-existing (but unused)typeargument on theviewsGraphQL field. An agent can pass{"type": "FormBoardView", "only": true}and reach a token from a board id alone — no view id required, which matters because agents generally do not have one.[REQUIRED PRECONDITION], following the conventioncreate_columnuses forget_column_type_info.On exposing the token
The token is
SecureRandom.hex(16)— a real capability, not an id. But it is a deliberately public one: it is the form's shareable address, andwkf.msexists to spread it. Returning it adds no new exposure — the resolver is already gated oncan?(:read, @board), it is visible in the UI to anyone with board read, and it is documented public API. It also does not bypassfeatures.password.enabled,requireLogin, orisInternal.Also fixed
update_form's description told agents to callget_form"to resolve the formToken" — circular, sinceget_formrequires the token as input. Reworded to "to confirm the formToken points at the intended form".Testing
73/73suites,1370/1370tests pass;tsc --noEmitclean; eslint clean.form-token.test.tscovers bare tokens, full URLs, short-link redirects, redirect failure, and rejection messages.get-board-info.test.tscases cover token parsing, never exposingview_specific_data_str, omission for"{}"/ unparseable / array / null / missing, and thefilters.views.typepassthrough (including that it is dropped when the views section is excluded).Note
Version bumped to
5.68.0with aCHANGELOG.mdentry, per the repo's manual-versioning rule.🤖 Generated with Claude Code