From 19cf40f9b751957c098a9fc8595ec61279afd116 Mon Sep 17 00:00:00 2001 From: Matthew Podwysocki Date: Tue, 13 Jan 2026 10:00:21 -0500 Subject: [PATCH 01/11] Add MCP elicitation for secure preview token handling - Implement token elicitation to keep tokens out of chat history - Users can provide, create, or auto-create preview tokens - Add session-level token storage to avoid repeated prompts - Support URL-restricted tokens for enhanced security - Maintain backward compatibility with direct token provision - Update README with security best practices Security improvements: - Preview tokens no longer appear in chat history via elicitation - Users can create URL-restricted tokens inline - Token caching reduces friction while maintaining security Co-Authored-By: Claude Sonnet 4.5 --- README.md | 20 +- .../PreviewStyleTool.input.schema.ts | 10 +- .../preview-style-tool/PreviewStyleTool.ts | 236 +++++++++++++++++- src/utils/tokenElicitation.ts | 163 ++++++++++++ 4 files changed, 418 insertions(+), 11 deletions(-) create mode 100644 src/utils/tokenElicitation.ts diff --git a/README.md b/README.md index 8d7a47e..281685c 100644 --- a/README.md +++ b/README.md @@ -185,11 +185,21 @@ Complete set of tools for managing Mapbox styles via the Styles API: - Input: `styleId` - Returns: Success confirmation -**PreviewStyleTool** - Generate preview URL for a Mapbox style using an existing public token - -- Input: `styleId`, `title` (optional), `zoomwheel` (optional), `zoom` (optional), `center` (optional), `bearing` (optional), `pitch` (optional) +**PreviewStyleTool** - Generate preview URL for a Mapbox style with secure token handling + +- Input: + - `styleId` (required): Style ID to preview + - `accessToken` (optional): Provide a specific public token (for backward compatibility) + - `useCustomToken` (optional): Force token selection dialog even if a token is cached + - `title` (optional): Show title in preview + - `zoomwheel` (optional): Enable zoom wheel control - Returns: URL to open the style preview in browser -- **Note**: This tool automatically fetches the first available public token from your account for the preview URL. Requires at least one public token with `styles:read` scope. +- **🔐 Secure Token Handling**: If `accessToken` is not provided, this tool uses MCP **elicitation** to securely request a preview token from you without storing it in chat history. You'll be prompted to: + 1. **Provide an existing token** - Paste a token you already have + 2. **Create a new preview token** - Create a new token with optional URL restrictions for enhanced security + 3. **Auto-create a basic token** - Let the tool create a simple preview token for you +- **Session Storage**: Your token choice is cached for the session, so you only need to provide it once +- **Best Practice**: Use URL-restricted tokens (option 2) to limit token usage to specific domains **ValidateStyleTool** - Validate Mapbox style JSON against the Mapbox Style Specification @@ -211,7 +221,7 @@ Complete set of tools for managing Mapbox styles via the Styles API: - **RetrieveStyleTool**: Requires `styles:download` scope - **UpdateStyleTool**: Requires `styles:write` scope - **DeleteStyleTool**: Requires `styles:write` scope -- **PreviewStyleTool**: Requires `tokens:read` scope (to list tokens) and at least one public token with `styles:read` scope +- **PreviewStyleTool**: Can work without token scopes via elicitation, or optionally accepts a direct public token. If using automatic token listing, requires `tokens:read` scope **Note:** The username is automatically extracted from the JWT token payload. diff --git a/src/tools/preview-style-tool/PreviewStyleTool.input.schema.ts b/src/tools/preview-style-tool/PreviewStyleTool.input.schema.ts index eec52c3..93a46b0 100644 --- a/src/tools/preview-style-tool/PreviewStyleTool.input.schema.ts +++ b/src/tools/preview-style-tool/PreviewStyleTool.input.schema.ts @@ -8,8 +8,16 @@ export const PreviewStyleSchema = z.object({ 'pk.', 'Invalid access token. Only public tokens (starting with pk.*) are allowed for preview URLs. Secret tokens (sk.*) cannot be used as they cannot be exposed in browser URLs.' ) + .optional() + .describe( + 'Mapbox public access token (optional). If not provided, you will be prompted to provide, create, or auto-create a preview token. Must start with pk.* and have styles:read permission. Secret tokens (sk.*) cannot be used as they cannot be exposed in browser URLs.' + ), + useCustomToken: z + .boolean() + .optional() + .default(false) .describe( - 'Mapbox public access token (required, must start with pk.* and have styles:read permission). Secret tokens (sk.*) cannot be used as they cannot be exposed in browser URLs. Please use an existing public token or get one from list_tokens_tool or create one with create_token_tool with styles:read permission.' + 'Force token selection dialog even if a preview token is already stored for this session. Useful when you want to use a different token.' ), title: z .boolean() diff --git a/src/tools/preview-style-tool/PreviewStyleTool.ts b/src/tools/preview-style-tool/PreviewStyleTool.ts index cf028cf..dee980d 100644 --- a/src/tools/preview-style-tool/PreviewStyleTool.ts +++ b/src/tools/preview-style-tool/PreviewStyleTool.ts @@ -8,6 +8,11 @@ import { } from './PreviewStyleTool.input.schema.js'; import { getUserNameFromToken } from '../../utils/jwtUtils.js'; import { isMcpUiEnabled } from '../../config/toolConfig.js'; +import { + elicitPreviewToken, + previewTokenStorage, + type ExistingTokenInfo +} from '../../utils/tokenElicitation.js'; export class PreviewStyleTool extends BaseTool { readonly name = 'preview_style_tool'; @@ -25,10 +30,121 @@ export class PreviewStyleTool extends BaseTool { super({ inputSchema: PreviewStyleSchema }); } - protected async execute(input: PreviewStyleInput): Promise { + protected async execute( + input: PreviewStyleInput, + serverAccessToken?: string + ): Promise { + let publicToken: string; let userName: string; + + // Step 1: Determine which token to use for preview + if (input.accessToken) { + // User provided token directly (backward compatibility) + publicToken = input.accessToken; + } else { + // No token provided - use elicitation flow + try { + // Get username from server access token to check storage + userName = getUserNameFromToken(serverAccessToken || ''); + } catch (error) { + return { + isError: true, + content: [ + { + type: 'text', + text: + 'Server access token is required when no preview token is provided. ' + + (error instanceof Error ? error.message : String(error)) + } + ] + }; + } + + // Check for stored preview token (unless user wants to use custom) + const storedToken = previewTokenStorage.get(userName); + if (storedToken && !input.useCustomToken) { + publicToken = storedToken; + } else { + // Need to elicit token from user + if (!this.server) { + return { + isError: true, + content: [ + { + type: 'text', + text: 'Server not initialized. Cannot elicit token from user.' + } + ] + }; + } + + // Get existing public tokens to show user + const existingTokens = await this.listPublicTokens(serverAccessToken); + + // Elicit token choice from user + const elicited = await elicitPreviewToken( + this.server.server, + existingTokens + ); + + // Handle user's choice + if (elicited.choice === 'provide') { + if (!elicited.token) { + return { + isError: true, + content: [ + { + type: 'text', + text: 'No token provided. Please provide a valid public token.' + } + ] + }; + } + publicToken = elicited.token; + } else if (elicited.choice === 'create') { + // Create new token with user's specifications + const created = await this.createPreviewToken( + serverAccessToken, + elicited.tokenNote, + elicited.urlRestrictions + ); + if (!created.success) { + return { + isError: true, + content: [ + { + type: 'text', + text: `Failed to create token: ${created.error}` + } + ] + }; + } + publicToken = created.token!; + } else { + // auto - create basic preview token + const created = await this.createPreviewToken(serverAccessToken); + if (!created.success) { + return { + isError: true, + content: [ + { + type: 'text', + text: `Failed to auto-create token: ${created.error}` + } + ] + }; + } + publicToken = created.token!; + } + + // Store token for future use + previewTokenStorage.set(userName, publicToken); + } + } + + // Step 2: Get username from the preview token try { - userName = getUserNameFromToken(input.accessToken); + userName = getUserNameFromToken(publicToken); } catch (error) { return { isError: true, @@ -41,9 +157,6 @@ export class PreviewStyleTool extends BaseTool { }; } - // Use the user-provided public token - const publicToken = input.accessToken; - // Build URL for the embeddable HTML endpoint const params = new URLSearchParams(); params.append('access_token', publicToken); @@ -94,4 +207,117 @@ export class PreviewStyleTool extends BaseTool { isError: false }; } + + /** + * List existing public tokens from the user's Mapbox account + */ + private async listPublicTokens( + accessToken?: string + ): Promise { + if (!accessToken) { + return []; + } + + try { + const userName = getUserNameFromToken(accessToken); + const response = await fetch( + `${MapboxApiBasedTool.mapboxApiEndpoint}tokens/v2/${userName}?access_token=${accessToken}` + ); + + if (!response.ok) { + // If we can't list tokens, return empty array (non-fatal) + return []; + } + + const data = await response.json(); + const tokens = data as Array<{ + id: string; + note: string; + scopes: string[]; + token?: string; + }>; + + // Filter to public tokens with styles:read scope + return tokens + .filter( + (t) => t.token?.startsWith('pk.') && t.scopes.includes('styles:read') + ) + .map((t) => ({ + id: t.id, + note: t.note || t.id, + scopes: t.scopes + })); + } catch { + // Non-fatal error - return empty array + return []; + } + } + + /** + * Create a new preview token via Mapbox API + */ + private async createPreviewToken( + accessToken?: string, + note?: string, + urlRestrictions?: string[] + ): Promise<{ success: boolean; token?: string; error?: string }> { + if (!accessToken) { + return { + success: false, + error: 'Server access token is required to create preview tokens' + }; + } + + try { + const userName = getUserNameFromToken(accessToken); + const tokenNote = + note || `MCP Preview Token - ${new Date().toISOString().split('T')[0]}`; + + const body: { + note: string; + scopes: string[]; + allowedUrls?: string[]; + } = { + note: tokenNote, + scopes: ['styles:read', 'styles:tiles', 'styles:download'] + }; + + if (urlRestrictions && urlRestrictions.length > 0) { + body.allowedUrls = urlRestrictions; + } + + const response = await fetch( + `${MapboxApiBasedTool.mapboxApiEndpoint}tokens/v2/${userName}?access_token=${accessToken}`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(body) + } + ); + + if (!response.ok) { + const errorText = await response.text(); + return { + success: false, + error: `Failed to create token: ${response.status} ${errorText}` + }; + } + + const data = (await response.json()) as { token: string }; + return { + success: true, + token: data.token + }; + } catch (error) { + return { + success: false, + error: + error instanceof Error + ? error.message + : 'Unknown error creating token' + }; + } + } } diff --git a/src/utils/tokenElicitation.ts b/src/utils/tokenElicitation.ts new file mode 100644 index 0000000..9b0a90c --- /dev/null +++ b/src/utils/tokenElicitation.ts @@ -0,0 +1,163 @@ +// Copyright (c) Mapbox, Inc. +// Licensed under the MIT License. + +import { Server } from '@modelcontextprotocol/sdk/server/index.js'; + +/** + * Token choice options for preview token elicitation + */ +export type TokenChoice = 'provide' | 'create' | 'auto'; + +/** + * Result of token elicitation + */ +export interface ElicitedTokenInfo { + choice: TokenChoice; + token?: string; + urlRestrictions?: string[]; + tokenNote?: string; +} + +/** + * Existing token info for display + */ +export interface ExistingTokenInfo { + id: string; + note: string; + scopes: string[]; +} + +/** + * Elicits preview token information from the user via MCP elicitation. + * This keeps the token out of chat history for better security. + * + * @param server - MCP Server instance + * @param existingTokens - List of user's existing public tokens + * @returns Elicited token information based on user's choice + */ +export async function elicitPreviewToken( + server: Server, + existingTokens: ExistingTokenInfo[] +): Promise { + const hasExistingTokens = existingTokens.length > 0; + const tokenList = hasExistingTokens + ? existingTokens + .map((t) => `- ${t.note || t.id}: ${t.scopes.join(', ')}`) + .join('\n') + : 'No existing public tokens found.'; + + const result = await server.elicitInput({ + message: `Preview Token Setup + +Preview URLs require a public token with styles:read scope. This token will be visible in the preview URL. + +${hasExistingTokens ? 'Your existing public tokens:\n' + tokenList : tokenList} + +For best security, consider using a URL-restricted token that only works on your domains.`, + requestedSchema: { + type: 'object', + properties: { + choice: { + type: 'string', + title: 'Token Option', + description: 'How would you like to provide the preview token?', + enum: ['provide', 'create', 'auto'], + enumNames: [ + 'I have a token to provide', + 'Create a new preview token with custom settings', + 'Auto-create a basic preview token for me' + ] + }, + token: { + type: 'string', + title: 'Your Token', + description: + 'Paste your public Mapbox token here (must have styles:read scope)', + minLength: 10 + }, + tokenNote: { + type: 'string', + title: 'Token Name (Optional)', + description: + 'A descriptive name for your new token (e.g., "Preview Token - Production")', + maxLength: 256 + }, + urlRestrictions: { + type: 'string', + title: 'URL Restrictions (Optional)', + description: + 'Comma-separated URLs to restrict token usage (e.g., "https://yourdomain.com/*,https://staging.yourdomain.com/*")' + } + }, + required: ['choice'] + } + }); + + // Check if user accepted or declined + if (result.action !== 'accept' || !result.content) { + throw new Error('Token elicitation was cancelled or declined by user'); + } + + // Parse the result + const choice = (result.content.choice as TokenChoice) || 'auto'; + const token = result.content.token as string | undefined; + const tokenNote = result.content.tokenNote as string | undefined; + const urlRestrictionsStr = result.content.urlRestrictions as + | string + | undefined; + + const urlRestrictions = urlRestrictionsStr + ? urlRestrictionsStr + .split(',') + .map((url) => url.trim()) + .filter((url) => url.length > 0) + : undefined; + + return { + choice, + token, + urlRestrictions, + tokenNote + }; +} + +/** + * Session-level storage for preview token preferences. + * In a real implementation, this could be stored in a database or cache. + */ +class PreviewTokenStorage { + private tokenCache = new Map(); + + /** + * Store a preview token for a specific username + */ + set(username: string, token: string): void { + this.tokenCache.set(username, token); + } + + /** + * Get stored preview token for a username + */ + get(username: string): string | undefined { + return this.tokenCache.get(username); + } + + /** + * Clear stored token for a username + */ + clear(username: string): void { + this.tokenCache.delete(username); + } + + /** + * Clear all stored tokens + */ + clearAll(): void { + this.tokenCache.clear(); + } +} + +/** + * Global preview token storage instance + */ +export const previewTokenStorage = new PreviewTokenStorage(); From 2b81c6778961c981cd8fd706713d4221a24b38c4 Mon Sep 17 00:00:00 2001 From: Matthew Podwysocki Date: Tue, 13 Jan 2026 10:30:31 -0500 Subject: [PATCH 02/11] Fix: Ensure preview tokens are created as public tokens (pk.*) Critical security fix for PreviewStyleTool: - Add `public: true` flag to token creation API request body - Validate that created tokens start with 'pk.' prefix - Prevent accidental creation of secret tokens (sk.*) which should never be exposed in browser URLs This ensures preview URLs always use public tokens that can be safely shared in preview URLs without security risk. Co-Authored-By: Claude Sonnet 4.5 --- src/tools/preview-style-tool/PreviewStyleTool.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/tools/preview-style-tool/PreviewStyleTool.ts b/src/tools/preview-style-tool/PreviewStyleTool.ts index dee980d..76ba8ad 100644 --- a/src/tools/preview-style-tool/PreviewStyleTool.ts +++ b/src/tools/preview-style-tool/PreviewStyleTool.ts @@ -277,9 +277,11 @@ export class PreviewStyleTool extends BaseTool { note: string; scopes: string[]; allowedUrls?: string[]; + public?: boolean; } = { note: tokenNote, - scopes: ['styles:read', 'styles:tiles', 'styles:download'] + scopes: ['styles:read', 'styles:tiles', 'styles:download'], + public: true // CRITICAL: Must be public token for browser URLs }; if (urlRestrictions && urlRestrictions.length > 0) { @@ -306,6 +308,15 @@ export class PreviewStyleTool extends BaseTool { } const data = (await response.json()) as { token: string }; + + // Validate that we got a public token (starts with pk.) + if (!data.token.startsWith('pk.')) { + return { + success: false, + error: `API returned a non-public token (${data.token.substring(0, 3)}...). Preview tokens must be public tokens (pk.*) that can be safely exposed in URLs.` + }; + } + return { success: true, token: data.token From 0e687432360d071e002fbea633d2eb374f332e19 Mon Sep 17 00:00:00 2001 From: Matthew Podwysocki Date: Tue, 13 Jan 2026 10:34:27 -0500 Subject: [PATCH 03/11] Fix: Use only public scopes to create public tokens (pk.*) Root cause: The Mapbox Tokens API automatically determines token type (public vs secret) based on the SCOPES requested, not an explicit parameter. Problem: - We were requesting 'styles:download' which is a SECRET scope - This forced the API to create a secret token (sk.*) instead of public (pk.*) - Secret tokens cannot be safely exposed in browser URLs Solution: - Changed scopes to only public scopes: ['styles:read', 'styles:tiles', 'fonts:read'] - These are sufficient for preview URLs and guarantee public token creation - Removed the unsupported 'public: true' parameter - Updated comments to explain the scope selection rationale Testing: Verified in MCP Inspector that auto-create now produces pk.* tokens Co-Authored-By: Claude Sonnet 4.5 --- src/tools/preview-style-tool/PreviewStyleTool.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tools/preview-style-tool/PreviewStyleTool.ts b/src/tools/preview-style-tool/PreviewStyleTool.ts index 76ba8ad..74f2432 100644 --- a/src/tools/preview-style-tool/PreviewStyleTool.ts +++ b/src/tools/preview-style-tool/PreviewStyleTool.ts @@ -277,11 +277,11 @@ export class PreviewStyleTool extends BaseTool { note: string; scopes: string[]; allowedUrls?: string[]; - public?: boolean; } = { note: tokenNote, - scopes: ['styles:read', 'styles:tiles', 'styles:download'], - public: true // CRITICAL: Must be public token for browser URLs + // CRITICAL: Only use public scopes to get a public token (pk.*) + // styles:download is a secret scope and would create sk.* token + scopes: ['styles:read', 'styles:tiles', 'fonts:read'] }; if (urlRestrictions && urlRestrictions.length > 0) { From f51feff5734a132c2c60d24c4615670e21ab1afd Mon Sep 17 00:00:00 2001 From: Matthew Podwysocki Date: Tue, 13 Jan 2026 11:00:39 -0500 Subject: [PATCH 04/11] Fix: Check client elicitation capability before using elicitInput() According to the MCP specification, servers must verify that the client supports elicitation capability before attempting to use elicitInput(). Changes: - Added client capability check before calling elicitPreviewToken() - Returns clear error message if client doesn't support elicitation - Suggests providing accessToken parameter directly as fallback - Prevents "Method not found" errors when client lacks capability This fixes the issue where tools using elicitation would fail on clients that don't advertise elicitation support in their capabilities. Reference: https://modelcontextprotocol.io/specification/2025-11-25/client/elicitation Co-Authored-By: Claude Sonnet 4.5 --- src/tools/preview-style-tool/PreviewStyleTool.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/tools/preview-style-tool/PreviewStyleTool.ts b/src/tools/preview-style-tool/PreviewStyleTool.ts index 74f2432..9a316e8 100644 --- a/src/tools/preview-style-tool/PreviewStyleTool.ts +++ b/src/tools/preview-style-tool/PreviewStyleTool.ts @@ -78,6 +78,22 @@ export class PreviewStyleTool extends BaseTool { }; } + // Check if client supports elicitation capability + const clientCapabilities = this.server.server.getClientCapabilities(); + if (!clientCapabilities?.elicitation) { + return { + isError: true, + content: [ + { + type: 'text', + text: + 'Preview token required but client does not support elicitation. ' + + 'Please provide an accessToken parameter directly, or use a client that supports MCP elicitation (e.g., Claude Desktop, Claude Code).' + } + ] + }; + } + // Get existing public tokens to show user const existingTokens = await this.listPublicTokens(serverAccessToken); From ec35e385be966761f92ffad34bb8f92634059f57 Mon Sep 17 00:00:00 2001 From: Matthew Podwysocki Date: Tue, 13 Jan 2026 11:04:56 -0500 Subject: [PATCH 05/11] Docs: Clarify varying MCP elicitation support across clients MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added documentation to clarify that MCP elicitation support varies by client: - MCP Inspector has full support for secure token elicitation - Claude Desktop does not support elicitation yet, but Claude intelligently falls back to offering token creation via create_token_tool - Other clients should check their documentation for elicitation support Changes: - Added "Note on MCP Elicitation Support" in Quick Start section - Updated PreviewStyleTool description with client-specific behavior - Clarified that tokens appear in chat history when elicitation is unavailable - Added visual indicators (✅/⚠️) for support status This helps users understand expected behavior based on their MCP client. Co-Authored-By: Claude Sonnet 4.5 --- README.md | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 281685c..8501818 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,12 @@ Get started by integrating with your preferred AI development environment: - [Cursor Integration](./docs/cursor-integration.md) - Cursor IDE integration - [VS Code Integration](./docs/vscode-integration.md) - Visual Studio Code with GitHub Copilot +**Note on MCP Elicitation Support**: Some tools (like `preview_style_tool`) use [MCP elicitation](https://modelcontextprotocol.io/specification/2025-11-25/client/elicitation) to securely request tokens without exposing them in chat history. Elicitation support varies by client: + +- **MCP Inspector**: ✅ Full support +- **Claude Desktop**: ⚠️ Not yet supported (Claude will fall back to creating tokens via chat) +- **Claude Code, Cursor, VS Code**: Check client documentation for elicitation support status + ### DXT Package Distribution This MCP server can be packaged as a DXT (Desktop Extension) file for easy distribution and installation. DXT is a standardized format for distributing local MCP servers, similar to browser extensions. @@ -194,12 +200,15 @@ Complete set of tools for managing Mapbox styles via the Styles API: - `title` (optional): Show title in preview - `zoomwheel` (optional): Enable zoom wheel control - Returns: URL to open the style preview in browser -- **🔐 Secure Token Handling**: If `accessToken` is not provided, this tool uses MCP **elicitation** to securely request a preview token from you without storing it in chat history. You'll be prompted to: - 1. **Provide an existing token** - Paste a token you already have - 2. **Create a new preview token** - Create a new token with optional URL restrictions for enhanced security - 3. **Auto-create a basic token** - Let the tool create a simple preview token for you -- **Session Storage**: Your token choice is cached for the session, so you only need to provide it once -- **Best Practice**: Use URL-restricted tokens (option 2) to limit token usage to specific domains +- **🔐 Secure Token Handling**: If `accessToken` is not provided, this tool attempts to use MCP **elicitation** to securely request a preview token without storing it in chat history. However, **elicitation support varies by client**: + - **MCP Inspector**: ✅ Full support - Shows secure form dialog with three options: + 1. **Provide an existing token** - Paste a token you already have + 2. **Create a new preview token** - Create a new token with optional URL restrictions for enhanced security + 3. **Auto-create a basic token** - Let the tool create a simple preview token for you + - **Claude Desktop**: ⚠️ Not yet supported - When elicitation is unavailable, Claude will intelligently offer to create a token for you using `create_token_tool` (token will appear in chat history) + - **Alternative**: Provide `accessToken` parameter directly for backward compatibility with any client +- **Session Storage**: Your token choice is cached for the session, so you only need to provide it once (when elicitation is supported) +- **Best Practice**: Use URL-restricted tokens to limit token usage to specific domains **ValidateStyleTool** - Validate Mapbox style JSON against the Mapbox Style Specification From b9a22462c2dc14fb6427145cd23b79a4d9aae290 Mon Sep 17 00:00:00 2001 From: Matthew Podwysocki Date: Tue, 13 Jan 2026 11:15:27 -0500 Subject: [PATCH 06/11] Docs: Update elicitation support status for Cursor and VS Code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Confirmed that Cursor and VS Code both have full MCP elicitation support. Updated README to accurately reflect support status: ✅ Full support: - MCP Inspector - Cursor - VS Code (with Copilot) ⚠️ Not yet supported: - Claude Desktop (falls back to create_token_tool) Co-Authored-By: Claude Sonnet 4.5 --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8501818..030f774 100644 --- a/README.md +++ b/README.md @@ -61,8 +61,10 @@ Get started by integrating with your preferred AI development environment: **Note on MCP Elicitation Support**: Some tools (like `preview_style_tool`) use [MCP elicitation](https://modelcontextprotocol.io/specification/2025-11-25/client/elicitation) to securely request tokens without exposing them in chat history. Elicitation support varies by client: - **MCP Inspector**: ✅ Full support +- **Cursor**: ✅ Full support +- **VS Code (with Copilot)**: ✅ Full support - **Claude Desktop**: ⚠️ Not yet supported (Claude will fall back to creating tokens via chat) -- **Claude Code, Cursor, VS Code**: Check client documentation for elicitation support status +- **Claude Code**: Check for latest support status ### DXT Package Distribution @@ -200,8 +202,8 @@ Complete set of tools for managing Mapbox styles via the Styles API: - `title` (optional): Show title in preview - `zoomwheel` (optional): Enable zoom wheel control - Returns: URL to open the style preview in browser -- **🔐 Secure Token Handling**: If `accessToken` is not provided, this tool attempts to use MCP **elicitation** to securely request a preview token without storing it in chat history. However, **elicitation support varies by client**: - - **MCP Inspector**: ✅ Full support - Shows secure form dialog with three options: +- **🔐 Secure Token Handling**: If `accessToken` is not provided, this tool attempts to use MCP **elicitation** to securely request a preview token without storing it in chat history. **Elicitation support varies by client**: + - **MCP Inspector, Cursor, VS Code**: ✅ Full support - Shows secure form dialog with three options: 1. **Provide an existing token** - Paste a token you already have 2. **Create a new preview token** - Create a new token with optional URL restrictions for enhanced security 3. **Auto-create a basic token** - Let the tool create a simple preview token for you From c974bcc0015c5f9600f5d3874dbd944154dae970 Mon Sep 17 00:00:00 2001 From: Matthew Podwysocki Date: Tue, 13 Jan 2026 11:37:25 -0500 Subject: [PATCH 07/11] Docs: Add Goose elicitation bug report and documentation Created comprehensive bug report for Goose's MCP elicitation timing issue where forms display after timeout instead of during tool execution. Added: - docs/goose-elicitation-bug-report.md - Detailed bug report for Goose team with reproduction steps, expected vs actual behavior, technical details, and suggested fix - Updated README to document Goose's known elicitation bug with link to bug report in both Quick Start and PreviewStyleTool sections Bug Summary: Goose advertises elicitation capability but displays forms after tool execution completes/times out, preventing user input. Co-Authored-By: Claude Sonnet 4.5 --- README.md | 2 + docs/goose-elicitation-bug-report.md | 177 +++++++++++++++++++++++++++ 2 files changed, 179 insertions(+) create mode 100644 docs/goose-elicitation-bug-report.md diff --git a/README.md b/README.md index 030f774..027825c 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,7 @@ Get started by integrating with your preferred AI development environment: - **MCP Inspector**: ✅ Full support - **Cursor**: ✅ Full support - **VS Code (with Copilot)**: ✅ Full support +- **Goose**: ⚠️ Known bug - Form displays after timeout ([bug report](./docs/goose-elicitation-bug-report.md)) - **Claude Desktop**: ⚠️ Not yet supported (Claude will fall back to creating tokens via chat) - **Claude Code**: Check for latest support status @@ -207,6 +208,7 @@ Complete set of tools for managing Mapbox styles via the Styles API: 1. **Provide an existing token** - Paste a token you already have 2. **Create a new preview token** - Create a new token with optional URL restrictions for enhanced security 3. **Auto-create a basic token** - Let the tool create a simple preview token for you + - **Goose**: ⚠️ Known bug - Form displays after timeout (see [bug report](./docs/goose-elicitation-bug-report.md)) - **Claude Desktop**: ⚠️ Not yet supported - When elicitation is unavailable, Claude will intelligently offer to create a token for you using `create_token_tool` (token will appear in chat history) - **Alternative**: Provide `accessToken` parameter directly for backward compatibility with any client - **Session Storage**: Your token choice is cached for the session, so you only need to provide it once (when elicitation is supported) diff --git a/docs/goose-elicitation-bug-report.md b/docs/goose-elicitation-bug-report.md new file mode 100644 index 0000000..d32434b --- /dev/null +++ b/docs/goose-elicitation-bug-report.md @@ -0,0 +1,177 @@ +# Goose MCP Elicitation Bug Report + +## Summary + +MCP elicitation forms display after tool execution timeout, preventing user input and making the elicitation feature unusable. + +## Environment + +- **Goose Version**: [Please specify] +- **MCP Server**: @mapbox/mcp-devkit-server v0.4.6 +- **MCP SDK Version**: @modelcontextprotocol/sdk v1.17.5 +- **Operating System**: macOS (confirmed), likely affects all platforms + +## Bug Description + +When an MCP tool calls `server.elicitInput()` to request user input, Goose advertises the `elicitation` capability but does not display the form in time for the user to interact with it. The form appears only **after** the tool call has timed out and completed, making elicitation unusable. + +## Steps to Reproduce + +1. Connect Goose to the Mapbox MCP DevKit Server +2. Call `preview_style_tool` without providing an `accessToken` parameter: + ``` + preview_style_tool({ styleId: "streets-v12" }) + ``` +3. Observe that: + - No elicitation form appears immediately + - Tool appears to hang/wait indefinitely + - After timeout period, tool fails or falls back + - **Then** the elicitation form appears in the UI + - Form is non-interactive/too late to provide input + +## Expected Behavior + +The elicitation form should: + +1. Appear **immediately** when `server.elicitInput()` is called +2. Block tool execution until user provides input or cancels +3. Allow user to interact with the form before any timeout +4. Return user input to the tool for processing + +This is how elicitation works correctly in: + +- MCP Inspector ✅ +- Cursor ✅ +- VS Code with GitHub Copilot ✅ + +## Actual Behavior + +The elicitation form: + +1. Does not appear when `server.elicitInput()` is called +2. Tool execution waits/hangs with no visible UI +3. Request times out after waiting period +4. Form appears **after** timeout in the UI +5. User never had opportunity to provide input +6. Creates misleading impression that elicitation is supported + +## Technical Details + +### Server-side code (working in other clients) + +```typescript +// Check if client supports elicitation capability +const clientCapabilities = this.server.server.getClientCapabilities(); +if (!clientCapabilities?.elicitation) { + return { + isError: true, + content: [{ type: 'text', text: 'Client does not support elicitation' }] + }; +} + +// Goose advertises elicitation capability, so this check passes ✅ + +// Attempt to elicit user input +const result = await server.elicitInput({ + message: 'Preview Token Setup...', + requestedSchema: { + type: 'object', + properties: { + choice: { + type: 'string', + enum: ['provide', 'create', 'auto'], + enumNames: [ + 'I have a token to provide', + 'Create a new preview token with custom settings', + 'Auto-create a basic preview token for me' + ] + }, + token: { type: 'string', minLength: 10 } + // ... other fields + }, + required: ['choice'] + } +}); + +// This await hangs indefinitely in Goose ❌ +// Form appears only after this times out +``` + +### What Goose advertises + +Goose correctly advertises elicitation capability during MCP handshake: + +```json +{ + "capabilities": { + "elicitation": {} + } +} +``` + +### Suspected Issue + +The elicitation form rendering appears to be: + +- Queued asynchronously rather than displayed synchronously +- Rendered after tool execution completes rather than during the `elicitInput()` call +- Not blocking the tool execution as required by MCP spec + +## Impact + +**High** - Renders MCP elicitation completely unusable in Goose: + +- Tools that require secure user input cannot function +- Users cannot use features designed to keep sensitive data out of chat history +- Creates poor UX with delayed/non-functional form + +## Workaround + +Users must provide sensitive parameters directly in tool calls: + +```typescript +preview_style_tool({ + styleId: 'streets-v12', + accessToken: 'pk.secret-token-in-chat-history' // Not ideal for security +}); +``` + +This defeats the purpose of elicitation (keeping tokens out of chat history). + +## References + +- MCP Elicitation Spec: https://modelcontextprotocol.io/specification/2025-11-25/client/elicitation +- MCP SDK elicitInput: https://github.com/modelcontextprotocol/sdk +- Issue discovered in PR: https://github.com/mapbox/mcp-devkit-server/pull/57 + +## Suggested Fix + +The elicitation form should be displayed **synchronously** when the server calls `elicitInput()`: + +1. Server sends elicitation request via MCP protocol +2. Goose immediately renders form UI (blocking) +3. User interacts with form +4. Form submission/cancellation returns to server +5. Tool execution continues with result + +The form render should **not** be queued or delayed until after tool completion. + +## Additional Context + +This bug was discovered while implementing secure token handling for the Mapbox MCP DevKit Server. The same code works perfectly in MCP Inspector, Cursor, and VS Code, suggesting the issue is specific to Goose's elicitation implementation. + +## Testing + +To verify a fix: + +1. Install @mapbox/mcp-devkit-server: `npx @modelcontextprotocol/create-server mapbox` +2. Configure with a Mapbox access token +3. Call `preview_style_tool` without `accessToken` parameter +4. Verify form appears **immediately** and accepts user input **before** timeout +5. Verify tool completes successfully with user-provided token + +--- + +**Report Date**: 2026-01-13 +**Reporter**: Mapbox MCP DevKit Server Team +**Goose Team**: Please let us know if you need any additional information or test cases! From ffdc83a34de596d170d8a3111e81d068bf0a9918 Mon Sep 17 00:00:00 2001 From: Matthew Podwysocki Date: Tue, 13 Jan 2026 11:47:06 -0500 Subject: [PATCH 08/11] Docs: Link to filed Goose elicitation bug issue Updated bug report and README to reference the filed GitHub issue: https://github.com/block/goose/issues/6471 This allows users and developers to track the bug status directly with the Goose team. Co-Authored-By: Claude Sonnet 4.5 --- README.md | 4 ++-- docs/goose-elicitation-bug-report.md | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 027825c..b922624 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ Get started by integrating with your preferred AI development environment: - **MCP Inspector**: ✅ Full support - **Cursor**: ✅ Full support - **VS Code (with Copilot)**: ✅ Full support -- **Goose**: ⚠️ Known bug - Form displays after timeout ([bug report](./docs/goose-elicitation-bug-report.md)) +- **Goose**: ⚠️ Known bug - Form displays after timeout ([goose#6471](https://github.com/block/goose/issues/6471)) - **Claude Desktop**: ⚠️ Not yet supported (Claude will fall back to creating tokens via chat) - **Claude Code**: Check for latest support status @@ -208,7 +208,7 @@ Complete set of tools for managing Mapbox styles via the Styles API: 1. **Provide an existing token** - Paste a token you already have 2. **Create a new preview token** - Create a new token with optional URL restrictions for enhanced security 3. **Auto-create a basic token** - Let the tool create a simple preview token for you - - **Goose**: ⚠️ Known bug - Form displays after timeout (see [bug report](./docs/goose-elicitation-bug-report.md)) + - **Goose**: ⚠️ Known bug - Form displays after timeout ([goose#6471](https://github.com/block/goose/issues/6471)) - **Claude Desktop**: ⚠️ Not yet supported - When elicitation is unavailable, Claude will intelligently offer to create a token for you using `create_token_tool` (token will appear in chat history) - **Alternative**: Provide `accessToken` parameter directly for backward compatibility with any client - **Session Storage**: Your token choice is cached for the session, so you only need to provide it once (when elicitation is supported) diff --git a/docs/goose-elicitation-bug-report.md b/docs/goose-elicitation-bug-report.md index d32434b..8aef87a 100644 --- a/docs/goose-elicitation-bug-report.md +++ b/docs/goose-elicitation-bug-report.md @@ -1,5 +1,7 @@ # Goose MCP Elicitation Bug Report +**Status**: 🐛 Filed - https://github.com/block/goose/issues/6471 + ## Summary MCP elicitation forms display after tool execution timeout, preventing user input and making the elicitation feature unusable. From 08b2460e51a4d895751d3d019c3335fef736da63 Mon Sep 17 00:00:00 2001 From: Matthew Podwysocki Date: Tue, 13 Jan 2026 11:49:15 -0500 Subject: [PATCH 09/11] Remove redundant Goose bug report file Bug is now tracked on GitHub at https://github.com/block/goose/issues/6471 No need to maintain a duplicate markdown file in the repo. Co-Authored-By: Claude Sonnet 4.5 --- docs/goose-elicitation-bug-report.md | 179 --------------------------- 1 file changed, 179 deletions(-) delete mode 100644 docs/goose-elicitation-bug-report.md diff --git a/docs/goose-elicitation-bug-report.md b/docs/goose-elicitation-bug-report.md deleted file mode 100644 index 8aef87a..0000000 --- a/docs/goose-elicitation-bug-report.md +++ /dev/null @@ -1,179 +0,0 @@ -# Goose MCP Elicitation Bug Report - -**Status**: 🐛 Filed - https://github.com/block/goose/issues/6471 - -## Summary - -MCP elicitation forms display after tool execution timeout, preventing user input and making the elicitation feature unusable. - -## Environment - -- **Goose Version**: [Please specify] -- **MCP Server**: @mapbox/mcp-devkit-server v0.4.6 -- **MCP SDK Version**: @modelcontextprotocol/sdk v1.17.5 -- **Operating System**: macOS (confirmed), likely affects all platforms - -## Bug Description - -When an MCP tool calls `server.elicitInput()` to request user input, Goose advertises the `elicitation` capability but does not display the form in time for the user to interact with it. The form appears only **after** the tool call has timed out and completed, making elicitation unusable. - -## Steps to Reproduce - -1. Connect Goose to the Mapbox MCP DevKit Server -2. Call `preview_style_tool` without providing an `accessToken` parameter: - ``` - preview_style_tool({ styleId: "streets-v12" }) - ``` -3. Observe that: - - No elicitation form appears immediately - - Tool appears to hang/wait indefinitely - - After timeout period, tool fails or falls back - - **Then** the elicitation form appears in the UI - - Form is non-interactive/too late to provide input - -## Expected Behavior - -The elicitation form should: - -1. Appear **immediately** when `server.elicitInput()` is called -2. Block tool execution until user provides input or cancels -3. Allow user to interact with the form before any timeout -4. Return user input to the tool for processing - -This is how elicitation works correctly in: - -- MCP Inspector ✅ -- Cursor ✅ -- VS Code with GitHub Copilot ✅ - -## Actual Behavior - -The elicitation form: - -1. Does not appear when `server.elicitInput()` is called -2. Tool execution waits/hangs with no visible UI -3. Request times out after waiting period -4. Form appears **after** timeout in the UI -5. User never had opportunity to provide input -6. Creates misleading impression that elicitation is supported - -## Technical Details - -### Server-side code (working in other clients) - -```typescript -// Check if client supports elicitation capability -const clientCapabilities = this.server.server.getClientCapabilities(); -if (!clientCapabilities?.elicitation) { - return { - isError: true, - content: [{ type: 'text', text: 'Client does not support elicitation' }] - }; -} - -// Goose advertises elicitation capability, so this check passes ✅ - -// Attempt to elicit user input -const result = await server.elicitInput({ - message: 'Preview Token Setup...', - requestedSchema: { - type: 'object', - properties: { - choice: { - type: 'string', - enum: ['provide', 'create', 'auto'], - enumNames: [ - 'I have a token to provide', - 'Create a new preview token with custom settings', - 'Auto-create a basic preview token for me' - ] - }, - token: { type: 'string', minLength: 10 } - // ... other fields - }, - required: ['choice'] - } -}); - -// This await hangs indefinitely in Goose ❌ -// Form appears only after this times out -``` - -### What Goose advertises - -Goose correctly advertises elicitation capability during MCP handshake: - -```json -{ - "capabilities": { - "elicitation": {} - } -} -``` - -### Suspected Issue - -The elicitation form rendering appears to be: - -- Queued asynchronously rather than displayed synchronously -- Rendered after tool execution completes rather than during the `elicitInput()` call -- Not blocking the tool execution as required by MCP spec - -## Impact - -**High** - Renders MCP elicitation completely unusable in Goose: - -- Tools that require secure user input cannot function -- Users cannot use features designed to keep sensitive data out of chat history -- Creates poor UX with delayed/non-functional form - -## Workaround - -Users must provide sensitive parameters directly in tool calls: - -```typescript -preview_style_tool({ - styleId: 'streets-v12', - accessToken: 'pk.secret-token-in-chat-history' // Not ideal for security -}); -``` - -This defeats the purpose of elicitation (keeping tokens out of chat history). - -## References - -- MCP Elicitation Spec: https://modelcontextprotocol.io/specification/2025-11-25/client/elicitation -- MCP SDK elicitInput: https://github.com/modelcontextprotocol/sdk -- Issue discovered in PR: https://github.com/mapbox/mcp-devkit-server/pull/57 - -## Suggested Fix - -The elicitation form should be displayed **synchronously** when the server calls `elicitInput()`: - -1. Server sends elicitation request via MCP protocol -2. Goose immediately renders form UI (blocking) -3. User interacts with form -4. Form submission/cancellation returns to server -5. Tool execution continues with result - -The form render should **not** be queued or delayed until after tool completion. - -## Additional Context - -This bug was discovered while implementing secure token handling for the Mapbox MCP DevKit Server. The same code works perfectly in MCP Inspector, Cursor, and VS Code, suggesting the issue is specific to Goose's elicitation implementation. - -## Testing - -To verify a fix: - -1. Install @mapbox/mcp-devkit-server: `npx @modelcontextprotocol/create-server mapbox` -2. Configure with a Mapbox access token -3. Call `preview_style_tool` without `accessToken` parameter -4. Verify form appears **immediately** and accepts user input **before** timeout -5. Verify tool completes successfully with user-provided token - ---- - -**Report Date**: 2026-01-13 -**Reporter**: Mapbox MCP DevKit Server Team -**Goose Team**: Please let us know if you need any additional information or test cases! From 3c6f8a629d5603255b0e53484726f8bd0de734cb Mon Sep 17 00:00:00 2001 From: Matthew Podwysocki Date: Tue, 13 Jan 2026 14:53:44 -0500 Subject: [PATCH 10/11] Tests: Add unit tests for elicitation and token storage Added comprehensive test coverage for the new elicitation features: Token Storage Tests (test/utils/tokenElicitation.test.ts): - Store and retrieve tokens by username - Return undefined for non-existent username - Overwrite existing tokens - Store tokens for multiple users independently - Clear specific username token - Clear all tokens - Handle edge cases (empty string, special characters) PreviewStyleTool Elicitation Tests: - Error when no accessToken and no server token - Backward compatibility when accessToken provided directly Test Results: All 527 tests pass (12 new tests added) These tests ensure the elicitation feature works correctly and maintains backward compatibility with existing usage patterns. Co-Authored-By: Claude Sonnet 4.5 --- .../PreviewStyleTool.test.ts | 44 ++++++++++ test/utils/tokenElicitation.test.ts | 85 +++++++++++++++++++ 2 files changed, 129 insertions(+) create mode 100644 test/utils/tokenElicitation.test.ts diff --git a/test/tools/preview-style-tool/PreviewStyleTool.test.ts b/test/tools/preview-style-tool/PreviewStyleTool.test.ts index e8315c0..32c6793 100644 --- a/test/tools/preview-style-tool/PreviewStyleTool.test.ts +++ b/test/tools/preview-style-tool/PreviewStyleTool.test.ts @@ -195,4 +195,48 @@ describe('PreviewStyleTool', () => { // Clean up delete process.env.ENABLE_MCP_UI; }); + + describe('elicitation behavior', () => { + it('returns error when no accessToken and no valid server token', async () => { + const tool = new PreviewStyleTool(); + + // Remove env var temporarily to test error path + const oldToken = process.env.MAPBOX_ACCESS_TOKEN; + delete process.env.MAPBOX_ACCESS_TOKEN; + + const result = await tool.run({ + styleId: 'test-style' + // No accessToken, no authInfo.token either + }); + + expect(result.isError).toBe(true); + expect(result.content[0]).toMatchObject({ + type: 'text', + text: expect.stringContaining( + 'Server access token is required when no preview token is provided' + ) + }); + + // Restore env var + process.env.MAPBOX_ACCESS_TOKEN = oldToken; + }); + + it('works with backward compatibility when accessToken is provided', async () => { + const tool = new PreviewStyleTool(); + // Even without server initialization, providing accessToken directly should work + + const result = await tool.run({ + styleId: 'test-style', + accessToken: TEST_ACCESS_TOKEN + }); + + expect(result.isError).toBe(false); + expect(result.content[0]).toMatchObject({ + type: 'text', + text: expect.stringContaining( + '/styles/v1/test-user/test-style.html?access_token=pk.' + ) + }); + }); + }); }); diff --git a/test/utils/tokenElicitation.test.ts b/test/utils/tokenElicitation.test.ts new file mode 100644 index 0000000..dec2f92 --- /dev/null +++ b/test/utils/tokenElicitation.test.ts @@ -0,0 +1,85 @@ +// Copyright (c) Mapbox, Inc. +// Licensed under the MIT License. + +import { describe, it, expect, beforeEach } from 'vitest'; +import { previewTokenStorage } from '../../src/utils/tokenElicitation.js'; + +describe('PreviewTokenStorage', () => { + // Clean up before each test to ensure isolation + beforeEach(() => { + previewTokenStorage.clearAll(); + }); + + it('stores and retrieves tokens by username', () => { + previewTokenStorage.set('test-user', 'pk.test-token-123'); + expect(previewTokenStorage.get('test-user')).toBe('pk.test-token-123'); + }); + + it('returns undefined for non-existent username', () => { + expect(previewTokenStorage.get('non-existent-user')).toBeUndefined(); + }); + + it('overwrites existing token for same username', () => { + previewTokenStorage.set('test-user', 'pk.old-token'); + previewTokenStorage.set('test-user', 'pk.new-token'); + expect(previewTokenStorage.get('test-user')).toBe('pk.new-token'); + }); + + it('stores tokens for multiple users independently', () => { + previewTokenStorage.set('user1', 'pk.token1'); + previewTokenStorage.set('user2', 'pk.token2'); + previewTokenStorage.set('user3', 'pk.token3'); + + expect(previewTokenStorage.get('user1')).toBe('pk.token1'); + expect(previewTokenStorage.get('user2')).toBe('pk.token2'); + expect(previewTokenStorage.get('user3')).toBe('pk.token3'); + }); + + it('clears specific username token', () => { + previewTokenStorage.set('user1', 'pk.token1'); + previewTokenStorage.set('user2', 'pk.token2'); + + previewTokenStorage.clear('user1'); + + expect(previewTokenStorage.get('user1')).toBeUndefined(); + expect(previewTokenStorage.get('user2')).toBe('pk.token2'); // Other token unaffected + }); + + it('clearing non-existent username does not throw', () => { + expect(() => { + previewTokenStorage.clear('non-existent-user'); + }).not.toThrow(); + }); + + it('clears all tokens', () => { + previewTokenStorage.set('user1', 'pk.token1'); + previewTokenStorage.set('user2', 'pk.token2'); + previewTokenStorage.set('user3', 'pk.token3'); + + previewTokenStorage.clearAll(); + + expect(previewTokenStorage.get('user1')).toBeUndefined(); + expect(previewTokenStorage.get('user2')).toBeUndefined(); + expect(previewTokenStorage.get('user3')).toBeUndefined(); + }); + + it('works correctly after clearAll and new sets', () => { + previewTokenStorage.set('user1', 'pk.old-token'); + previewTokenStorage.clearAll(); + previewTokenStorage.set('user2', 'pk.new-token'); + + expect(previewTokenStorage.get('user1')).toBeUndefined(); + expect(previewTokenStorage.get('user2')).toBe('pk.new-token'); + }); + + it('handles empty string username', () => { + previewTokenStorage.set('', 'pk.empty-user-token'); + expect(previewTokenStorage.get('')).toBe('pk.empty-user-token'); + }); + + it('handles special characters in username', () => { + const specialUsername = 'user@example.com'; + previewTokenStorage.set(specialUsername, 'pk.special-token'); + expect(previewTokenStorage.get(specialUsername)).toBe('pk.special-token'); + }); +}); From 90cbd1b48176a68a9d4fbcdf736a1423ef14d97d Mon Sep 17 00:00:00 2001 From: Matthew Podwysocki Date: Wed, 14 Jan 2026 11:11:16 -0500 Subject: [PATCH 11/11] Docs: Confirm Claude Code does not support elicitation yet MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tested preview_style_tool directly via MCP and confirmed that Claude Code does not advertise elicitation capability. The tool correctly returns the error message we designed for clients without elicitation support. Updated README to reflect: - Claude Code: ⚠️ Not yet supported (provide accessToken directly) - Grouped with Claude Desktop in the "not yet supported" category This was confirmed by calling the tool through the registered MCP server and observing the capability check work as expected. Co-Authored-By: Claude Sonnet 4.5 --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b922624..27b2377 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ Get started by integrating with your preferred AI development environment: - **VS Code (with Copilot)**: ✅ Full support - **Goose**: ⚠️ Known bug - Form displays after timeout ([goose#6471](https://github.com/block/goose/issues/6471)) - **Claude Desktop**: ⚠️ Not yet supported (Claude will fall back to creating tokens via chat) -- **Claude Code**: Check for latest support status +- **Claude Code**: ⚠️ Not yet supported (provide `accessToken` parameter directly) ### DXT Package Distribution @@ -209,7 +209,7 @@ Complete set of tools for managing Mapbox styles via the Styles API: 2. **Create a new preview token** - Create a new token with optional URL restrictions for enhanced security 3. **Auto-create a basic token** - Let the tool create a simple preview token for you - **Goose**: ⚠️ Known bug - Form displays after timeout ([goose#6471](https://github.com/block/goose/issues/6471)) - - **Claude Desktop**: ⚠️ Not yet supported - When elicitation is unavailable, Claude will intelligently offer to create a token for you using `create_token_tool` (token will appear in chat history) + - **Claude Desktop, Claude Code**: ⚠️ Not yet supported - Provide `accessToken` parameter directly, or Claude will intelligently offer to create a token for you using `create_token_tool` (token will appear in chat history) - **Alternative**: Provide `accessToken` parameter directly for backward compatibility with any client - **Session Storage**: Your token choice is cached for the session, so you only need to provide it once (when elicitation is supported) - **Best Practice**: Use URL-restricted tokens to limit token usage to specific domains