Skip to content

feat: list_public_tokens_tool for pages built outside the chat - #304

Merged
mattpodwysocki merged 4 commits into
mainfrom
feat/list-public-tokens-tool
Oct 28, 2026
Merged

mattpodwysocki merged 4 commits into
mainfrom
feat/list-public-tokens-tool

Conversation

@mattpodwysocki

@mattpodwysocki mattpodwysocki commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Part of #281 (option 1, widened slightly)

Why

An agent building a site or app with a Mapbox map (ChatGPT/Codex, Claude Code, artifacts) had to ask the user for a public token. render_map_tool only helps inside the chat. This gives the agent the token, and steers it toward a URL-restricted one for anything published.

What

list_public_tokens_tool lists the account's public (pk.) tokens: name, whether it's the default, URL restrictions and scopes. It also returns a recommended token and guidance.

  • Safer token first: pass site_url (where the page will run) and it recommends a token restricted to that URL when the account has one. Otherwise it recommends the default token and says a published site should use a URL-restricted one, with the link to create it. Anyone who copies an unrestricted pk token can use it, and usage bills to the account.
  • Public tokens only: it asks the Tokens API for usage=pk and drops anything else that comes back, so secret and temporary tokens never reach the model. A test feeds it sk and tk entries to check.
  • Can't create tokens: that needs tokens:write. Hosted already removes DevKit's create_token_tool on purpose, and people who want to mint restricted tokens can use DevKit.
  • No new scope: listing needs tokens:read, which the in-chat map already uses (resolveMapboxPublicToken) to put the default pk token into every map page.
  • Fallbacks: with a pk. access token it returns that token without calling the API; when listing fails and MAPBOX_PUBLIC_TOKEN is set, it returns that. A 401/403 says the token needs tokens:read.
  • Server instructions: one line telling the model to use it instead of asking the user for a token when it builds a page outside the chat. Added only when the tool is enabled.
  • URL matching for site_url is best effort (scheme-insensitive prefix match, *.domain wildcards); Mapbox makes the final call when the page loads.

This doesn't generate map code: the agent still writes the GL JS (options 3 and 4 in #281 can come back later if that turns out poorly).

Testing

  • 22 new tests: the request (account path, usage=pk), listing and the recommended token with and without site_url, the fallback reason when nothing matches, sk/tk entries never passed through, the object response form, the pk access token path (no API call), the tokens:read hint on 403, the MAPBOX_PUBLIC_TOKEN fallback, an account with no public tokens, and 11 urlMatches cases (including example.com.evil.org not matching example.com). Plus a server-instructions test.
  • Typecheck clean, 97 files / 1109 tests pass, lint 0 errors.
  • Built server over stdio: the tool lists (31 tools, read-only, with an output schema), the instructions mention it, and with a pk token it returns that token and the tokens:read note.
  • Live Tokens API with a secret token and site_url: https://example.com: listed the account's 3 public tokens (no secret tokens), none URL-restricted, so it recommended the default public token and said to create a restricted one for a published site.

🤖 Generated with Claude Code

Lists the account's public (pk.) tokens with their URL restrictions and
recommends one: a token restricted to site_url when there is one,
otherwise the default with advice to use a URL-restricted token for a
published site. Never returns secret or temporary tokens and can't create
tokens. Server instructions point page building at it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mattpodwysocki
mattpodwysocki requested a review from a team as a code owner October 26, 2026 16:50
@mattpodwysocki mattpodwysocki added the enhancement New feature or request label Oct 26, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment on lines +199 to +213
/** MAPBOX_PUBLIC_TOKEN when it's set, otherwise the error. */
private envFallbackOr(message: string): CallToolResult {
const envToken = process.env.MAPBOX_PUBLIC_TOKEN;
if (envToken?.startsWith('pk.')) {
return this.result({
tokens: [],
recommended: {
token: envToken,
reason: `The public token this server is configured with (MAPBOX_PUBLIC_TOKEN). ${message}`
},
guidance: GUIDANCE
});
}
return { content: [{ type: 'text', text: message }], isError: true };
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is there any concern here on the fallback? For instance if I'm hosting this and my env has a MAPBOX_PUBLIC_TOKEN present in the environment - what happens? How do we ensure it is using the users token, not the servers?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch, that was a real leak. On a hosted server, a caller whose token couldn't list tokens would have gotten the operator's MAPBOX_PUBLIC_TOKEN. Fixed in 6564c75: the fallback now only kicks in when the request is running on the server's own MAPBOX_ACCESS_TOKEN (a local install). A hosted caller with their own bearer token just gets the error. I didn't match on the username since that claim in the token isn't verified. Added a test that a hosted caller never sees the env token.

};
}
}
const fallback = tokens.find((t) => t.default) ?? tokens[0];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If no default is found, could this result in a token restricted to other urls? would a check to make sure there are not restricted urls help?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yep, it could have. Fixed in 6564c75: the fallback only picks from tokens with no URL restrictions, preferring the default, then any unrestricted one. If every public token is restricted to some other site, nothing gets recommended and the text says so. Tests cover both cases.

mattpodwysocki and others added 2 commits October 27, 2026 14:13
Only fall back to MAPBOX_PUBLIC_TOKEN when the call runs on the server's
own MAPBOX_ACCESS_TOKEN, and never recommend a token restricted to other
URLs. From review on #304.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mattpodwysocki
mattpodwysocki merged commit 0bcd7f9 into main Oct 28, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants