feat: preview_style_tool and style_comparison_tool no longer require a token - #136
Merged
mattpodwysocki merged 2 commits intoAug 24, 2026
Conversation
…a token Both previously made accessToken a required pk.* input, so a caller had to already have (or separately go create) a public token before either tool would do anything, even for a one-off inline preview. accessToken is now optional on both: when omitted, the tool auto-generates a preview token from the server's own access token via a new shared mintScopedPreviewToken utility (the same tokens/v2 minting pattern MapPreviewUIResource already used for geojson_preview_tool, extracted so it's not duplicated a third time), so a first call needs no setup at all. Pass share: true alongside your own accessToken to get a link built from a token you manage yourself, for when something durable/shareable is actually wanted. share: true without accessToken is a clear validation error, not a silent fallback. The two tools' auto-mint tokens differ in shape, confirmed live against the real API rather than assumed: preview_style_tool mints a short-lived (~1h) tk.* token, since the Styles API's embeddable HTML preview page and GL JS both accept it. style_comparison_tool mints a non-expiring pk.* token instead, because agent.mapbox.com/tools/style-compare (which its comparison link embeds) validates the token prefix client-side and hard-rejects anything but pk.* — a tk.* token 400s there with "Configuration Error: Invalid token type", caught by loading the real returned URL in a browser before shipping this. The pk.* token stays narrowly scoped but doesn't self-expire; it persists on the account until manually revoked. Both tools now take httpRequest as a constructor dependency, matching every other network-calling tool in this repo. Verified live end-to-end in a real browser for both tools: a real custom style loaded via preview_style_tool's auto-minted token, and a real before/after comparison rendered via style_comparison_tool's. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Per README.md, the hosted MCP endpoint authenticates each request with its
own access token rather than a personal Mapbox account token, and that
token isn't granted tokens:write — so the auto-mint path added in the
previous commit can never succeed there. Without this, a hosted caller
omitting accessToken would hit a raw, misleading jwtUtils error
("MAPBOX_ACCESS_TOKEN is not in valid JWT format", naming an env var the
hosted deployment doesn't use) or a bare "Token API 403" with no next step.
Added describeAutoMintFailure() (src/utils/mintScopedPreviewToken.ts),
used by both preview_style_tool and style_comparison_tool's auto-mint
catch blocks, which recognizes both failure shapes (a server token that
doesn't parse as a Mapbox token at all, or one that parses fine but lacks
tokens:write) and rewrites them into a message that actually tells the
caller what to do: pass accessToken directly via list_tokens_tool/
create_token_tool. Anything else (e.g. a transient 5xx) passes through
unchanged rather than being guessed at.
Verified live via a real MCP client with a non-Mapbox-shaped bearer token
(simulating the hosted endpoint's auth shape) that the new message is
what actually gets returned end-to-end, not just in unit tests.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Valiunia
approved these changes
Aug 24, 2026
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.
What changed
Stacked on #135 (the preview-resource consolidation).
preview_style_toolandstyle_comparison_toolno longer require an existingaccessToken— both previously made it a requiredpk.*input, so a caller had to already have (or separately go create) a public token before either tool would do anything, even for a one-off inline look.accessTokenis now optional on both. Omit it and the tool auto-generates a preview token from the server's own access token; passshare: truealongside your ownaccessTokenwhen you actually want a link built from a token you manage yourself (for something durable/shareable).share: truewithoutaccessTokenis a clear validation error, not a silent fallback.Why
This came out of a Slack thread about the still-open elicitation PR (#57 — asks the user which token to use/create/paste, with a whole SEP-1036-compliant local-HTTP-server flow for securely collecting a pasted token). The question was whether we could avoid needing that whole flow for the common case. The answer: yes, for inline previews — elicitation is only genuinely needed when the goal is a durable, shareable link (which is a real, separate feature these tools already promise elsewhere — see README/
docs/STYLE_BUILDER.md/two prompts — not something this PR removes).#57stays exactly as valuable as before for thatshare: truecase; this PR just means the common "show me this now" case doesn't have to pay that cost.A real bug caught before shipping
The two tools' downstream consumers turned out to need different token shapes, confirmed live against the real API rather than assumed:
preview_style_tool's embeddable Styles API HTML preview page and GL JS both accept a short-livedtk.*token fine (the same kindgeojson_preview_tool's resource already auto-mints).style_comparison_tool's comparison link points atagent.mapbox.com/tools/style-compare, which validates the token prefix client-side and hard-rejects anything butpk.*— atk.*token there 400s with aConfiguration Error: Invalid token typepage. Caught this by actually loading the real returned URL in a browser, not just checkingisError: falsefrom the tool call.So
style_comparison_tool's auto-mint path requests a genuine, non-expiringpk.*token instead (confirmed: omitting the Tokens API'sexpiresfield is what makes it return"usage":"pk"rather than a temporary token) — still narrowly scoped tostyles:tiles/styles:read/fonts:read, just not self-expiring. Extracted the minting logic into a sharedmintScopedPreviewTokenutility (src/utils/mintScopedPreviewToken.ts) — used by both tools now, andMapPreviewUIResourcerefactored onto it too rather than keeping its own third copy of the same POST-to-tokens/v2logic.Verification
preview_style_tool's auto-minted URL and saw the real custom style render; loadedstyle_comparison_tool's and saw the real before/after swipe comparison render.share: truewithout a token, an explicit token overriding auto-mint even withshare: true, no server token to mint from, mint failure) and for the sharedmintScopedPreviewTokenutility directly (scopes/note/expiry in the request body, theexpiresInMs: nullno-expiry path, the AGI-905-style cross-account check, error propagation).httpRequestas a constructor dependency, matching every other network-calling tool in this repo (toolRegistry.ts/tools/index.tsupdated accordingly).npx tsc -p tsconfig.src.json,npx eslint: clean (0 errors; pre-existing warnings elsewhere untouched).🤖 Generated with Claude Code