diff --git a/README.md b/README.md index 8567e65..a5ff1e5 100644 --- a/README.md +++ b/README.md @@ -107,7 +107,7 @@ Complete set of tools for managing Mapbox styles via the Styles API: - **DeleteStyleTool**: Requires `styles:write` scope - **PreviewStyleTool**: Requires `tokens:read` scope (to list tokens) and at least one public token with `styles:read` scope -**Note:** The username is automatically extracted from the JWT token payload. Secret tokens (sk.\*) are required for write operations. +**Note:** The username is automatically extracted from the JWT token payload. **Example prompts:** @@ -130,7 +130,7 @@ Create a new Mapbox access token with specified scopes and optional URL restrict **Available Scopes:** -Public scopes (can be used with public tokens): +Available scopes for public tokens: - `styles:tiles` - Read styles as raster tiles - `styles:read` - Read styles @@ -138,23 +138,6 @@ Public scopes (can be used with public tokens): - `datasets:read` - Read datasets - `vision:read` - Read Vision API -Secret scopes (will create a secret token, visible only once): - -- `scopes:list` - List available scopes -- `map:read`, `map:write` - Read/write map configurations -- `user:read`, `user:write` - Read/write user data -- `uploads:read`, `uploads:list`, `uploads:write` - Manage uploads -- `fonts:list`, `fonts:write` - List/write fonts -- `styles:list`, `styles:write`, `styles:download`, `styles:protect` - Manage styles -- `tokens:read`, `tokens:write` - Read/write tokens -- `datasets:list`, `datasets:write` - List/write datasets -- `tilesets:list`, `tilesets:read`, `tilesets:write` - Manage tilesets -- `downloads:read` - Read downloads -- `vision:download` - Download Vision data -- `navigation:download` - Download navigation data -- `offline:read`, `offline:write` - Read/write offline data -- `user-feedback:read` - Read user feedback - **Example:** ```json @@ -168,7 +151,7 @@ Secret scopes (will create a secret token, visible only once): **Example prompts:** - "Create a new Mapbox token for my web app with styles:read and fonts:read permissions" -- "Generate a temporary token that expires in 30 minutes with styles:tiles and vision:read scopes" +- "Generate a token that expires in 30 minutes with styles:tiles and vision:read scopes" - "Create a restricted token that only works on https://myapp.com with styles:read, fonts:read, and datasets:read" #### list-tokens @@ -181,7 +164,7 @@ List Mapbox access tokens for the authenticated user with optional filtering and - `limit` (number, optional): Maximum number of tokens to return per page (1-100) - `sortby` (string, optional): Sort tokens by "created" or "modified" timestamp - `start` (string, optional): The token ID after which to start the listing (when provided, auto-pagination is disabled) -- `usage` (string, optional): Filter by token type: "pk" (public), "sk" (secret), or "tk" (temporary) +- `usage` (string, optional): Filter by token type: "pk" (public) **Pagination behavior:** @@ -194,17 +177,17 @@ List Mapbox access tokens for the authenticated user with optional filtering and { "limit": 10, "sortby": "created", - "usage": "sk" + "usage": "pk" } ``` **Example prompts:** - "List all my Mapbox tokens" -- "Show me my secret tokens sorted by creation date" +- "Show me my public tokens sorted by creation date" - "Find my default public token" - "List the 5 most recently modified tokens" -- "Show all temporary tokens in my account" +- "Show all public tokens in my account" ### Local processing tools diff --git a/assets/mcp_server_devkit.gif b/assets/mcp_server_devkit.gif index 027b2e4..8dff017 100644 Binary files a/assets/mcp_server_devkit.gif and b/assets/mcp_server_devkit.gif differ diff --git a/docs/claude-code-integration.md b/docs/claude-code-integration.md index 4c125d9..823e970 100644 --- a/docs/claude-code-integration.md +++ b/docs/claude-code-integration.md @@ -119,7 +119,7 @@ Once configured, you can use natural language to interact with Mapbox developmen ### Token Management - "Create a new token for my web app with styles:read and fonts:read permissions" -- "List all my secret tokens" +- "List all my public tokens" - "Show me my default public token" ### GeoJSON Processing diff --git a/docs/claude-desktop-integration.md b/docs/claude-desktop-integration.md index 4478f9e..4cd8726 100644 --- a/docs/claude-desktop-integration.md +++ b/docs/claude-desktop-integration.md @@ -132,7 +132,7 @@ Once configured, you can use natural language to interact with Mapbox developmen ### Token Management - "Create a new token for my web app with styles:read and fonts:read permissions" -- "List all my secret tokens" +- "List all my public tokens" - "Show me my default public token" ### GeoJSON Processing diff --git a/src/tools/__snapshots__/tool-naming-convention.test.ts.snap b/src/tools/__snapshots__/tool-naming-convention.test.ts.snap index 97e36e9..ef90c2e 100644 --- a/src/tools/__snapshots__/tool-naming-convention.test.ts.snap +++ b/src/tools/__snapshots__/tool-naming-convention.test.ts.snap @@ -24,7 +24,7 @@ exports[`Tool Naming Convention should maintain consistent tool list (snapshot t }, { "className": "CreateTokenTool", - "description": "Create a new Mapbox access token with specified scopes and optional URL restrictions. Token type (public/secret) is automatically determined by scopes: PUBLIC scopes (styles:tiles, styles:read, fonts:read, datasets:read, vision:read) create public tokens; SECRET scopes create secret tokens that are only visible once upon creation.", + "description": "Create a new Mapbox public access token with specified scopes and optional URL restrictions.", "toolName": "create_token_tool", }, { diff --git a/src/tools/create-token-tool/CreateTokenTool.schema.ts b/src/tools/create-token-tool/CreateTokenTool.schema.ts index d9aa613..5e58d92 100644 --- a/src/tools/create-token-tool/CreateTokenTool.schema.ts +++ b/src/tools/create-token-tool/CreateTokenTool.schema.ts @@ -1,7 +1,7 @@ import { z } from 'zod'; -// Public scopes that can be used with public tokens -const PUBLIC_SCOPES = [ +// Valid scopes for public tokens +const SCOPES = [ 'styles:tiles', 'styles:read', 'fonts:read', @@ -9,46 +9,12 @@ const PUBLIC_SCOPES = [ 'vision:read' ] as const; -// Secret scopes that can only be used with secret tokens -const SECRET_SCOPES = [ - 'scopes:list', - 'map:read', - 'map:write', - 'user:read', - 'user:write', - 'uploads:read', - 'uploads:list', - 'uploads:write', - 'fonts:list', - 'fonts:write', - 'styles:write', - 'styles:list', - 'styles:download', - 'styles:protect', - 'tokens:read', - 'tokens:write', - 'datasets:list', - 'datasets:write', - 'tilesets:list', - 'tilesets:read', - 'tilesets:write', - 'downloads:read', - 'vision:download', - 'navigation:download', - 'offline:read', - 'offline:write', - 'user-feedback:read' -] as const; - -// All valid scopes -const ALL_SCOPES = [...PUBLIC_SCOPES, ...SECRET_SCOPES] as const; - export const CreateTokenSchema = z.object({ note: z.string().describe('Description of the token'), scopes: z - .array(z.enum(ALL_SCOPES)) + .array(z.enum(SCOPES)) .describe( - 'Array of scopes/permissions for the token. PUBLIC scopes (styles:tiles, styles:read, fonts:read, datasets:read, vision:read) create a public token. SECRET scopes (all others) create a secret token. If any secret scope is included, the entire token becomes secret and will only be visible once upon creation.' + 'Array of scopes/permissions for the public token. Valid scopes: styles:tiles, styles:read, fonts:read, datasets:read, vision:read.' ), allowedUrls: z .array(z.string()) @@ -65,4 +31,4 @@ export const CreateTokenSchema = z.object({ export type CreateTokenInput = z.infer; // Export scopes for potential reuse -export { PUBLIC_SCOPES, SECRET_SCOPES, ALL_SCOPES }; +export { SCOPES }; diff --git a/src/tools/create-token-tool/CreateTokenTool.test.ts b/src/tools/create-token-tool/CreateTokenTool.test.ts index 25a3d8b..3a21527 100644 --- a/src/tools/create-token-tool/CreateTokenTool.test.ts +++ b/src/tools/create-token-tool/CreateTokenTool.test.ts @@ -30,7 +30,7 @@ describe('CreateTokenTool', () => { it('should have correct name and description', () => { expect(tool.name).toBe('create_token_tool'); expect(tool.description).toBe( - 'Create a new Mapbox access token with specified scopes and optional URL restrictions. Token type (public/secret) is automatically determined by scopes: PUBLIC scopes (styles:tiles, styles:read, fonts:read, datasets:read, vision:read) create public tokens; SECRET scopes create secret tokens that are only visible once upon creation.' + 'Create a new Mapbox public access token with specified scopes and optional URL restrictions.' ); }); @@ -219,11 +219,11 @@ describe('CreateTokenTool', () => { ]); }); - it('creates a temporary token with expiration', async () => { + it('creates a token with expiration', async () => { const expiresAt = '2024-12-31T23:59:59.000Z'; const mockResponse = { - token: 'tk.eyJ1IjoidGVzdHVzZXIiLCJhIjoiY2xwMTIzNDU2In0.test', - note: 'Temporary token', + token: 'pk.eyJ1IjoidGVzdHVzZXIiLCJhIjoiY2xwMTIzNDU2In0.test', + note: 'Token with expiration', id: 'cktest789', scopes: ['styles:read'], created: '2024-01-01T00:00:00.000Z', @@ -238,7 +238,7 @@ describe('CreateTokenTool', () => { } as Response); const result = await tool.run({ - note: 'Temporary token', + note: 'Token with expiration', scopes: ['styles:read'], expires: expiresAt }); @@ -253,36 +253,6 @@ describe('CreateTokenTool', () => { expect(requestBody.expires).toEqual(expiresAt); }); - it('logs warning when creating token with secret scopes', async () => { - const mockResponse = { - token: 'sk.eyJ1IjoidGVzdHVzZXIiLCJhIjoiY2xwMTIzNDU2In0.secret', - note: 'Secret token', - id: 'cksecret123', - scopes: ['tokens:write', 'styles:write'], - created: '2024-01-01T00:00:00.000Z', - modified: '2024-01-01T00:00:00.000Z' - }; - - const fetchMock = setupFetch(); - fetchMock.mockResolvedValueOnce({ - ok: true, - json: async () => mockResponse - } as Response); - - const result = await tool.run({ - note: 'Secret token', - scopes: ['tokens:write', 'styles:write'] - }); - - expect(result.isError).toBe(false); - - // Verify the warning was logged - expect(tool['log']).toHaveBeenCalledWith( - 'info', - 'CreateTokenTool: Creating a SECRET token due to secret scopes. This token will only be visible once upon creation.' - ); - }); - it('handles API errors gracefully', async () => { const fetchMock = setupFetch(); fetchMock.mockResolvedValueOnce({ @@ -295,7 +265,7 @@ describe('CreateTokenTool', () => { const result = await tool.run({ note: 'Test token', - scopes: ['tokens:write'] + scopes: ['styles:read'] }); expect(result.isError).toBe(true); diff --git a/src/tools/create-token-tool/CreateTokenTool.ts b/src/tools/create-token-tool/CreateTokenTool.ts index 8ecc6ba..debe9e4 100644 --- a/src/tools/create-token-tool/CreateTokenTool.ts +++ b/src/tools/create-token-tool/CreateTokenTool.ts @@ -1,8 +1,7 @@ import { MapboxApiBasedTool } from '../MapboxApiBasedTool.js'; import { CreateTokenSchema, - CreateTokenInput, - SECRET_SCOPES + CreateTokenInput } from './CreateTokenTool.schema.js'; export class CreateTokenTool extends MapboxApiBasedTool< @@ -10,7 +9,7 @@ export class CreateTokenTool extends MapboxApiBasedTool< > { readonly name = 'create_token_tool'; readonly description = - 'Create a new Mapbox access token with specified scopes and optional URL restrictions. Token type (public/secret) is automatically determined by scopes: PUBLIC scopes (styles:tiles, styles:read, fonts:read, datasets:read, vision:read) create public tokens; SECRET scopes create secret tokens that are only visible once upon creation.'; + 'Create a new Mapbox public access token with specified scopes and optional URL restrictions.'; constructor() { super({ inputSchema: CreateTokenSchema }); @@ -24,26 +23,9 @@ export class CreateTokenTool extends MapboxApiBasedTool< this.log( 'info', - `CreateTokenTool: Starting token creation with note: "${input.note}", scopes: ${JSON.stringify(input.scopes)}` + `CreateTokenTool: Creating public token with note: "${input.note}", scopes: ${JSON.stringify(input.scopes)}` ); - // Check if any secret scopes are being used - const hasSecretScopes = input.scopes.some((scope) => - SECRET_SCOPES.includes(scope as (typeof SECRET_SCOPES)[number]) - ); - - if (hasSecretScopes) { - this.log( - 'info', - 'CreateTokenTool: Creating a SECRET token due to secret scopes. This token will only be visible once upon creation.' - ); - } else { - this.log( - 'info', - 'CreateTokenTool: Creating a PUBLIC token (only public scopes detected).' - ); - } - const url = `${MapboxApiBasedTool.MAPBOX_API_ENDPOINT}tokens/v2/${username}?access_token=${accessToken}`; const body: { diff --git a/src/tools/list-tokens-tool/ListTokensTool.schema.ts b/src/tools/list-tokens-tool/ListTokensTool.schema.ts index f2c646a..8086b57 100644 --- a/src/tools/list-tokens-tool/ListTokensTool.schema.ts +++ b/src/tools/list-tokens-tool/ListTokensTool.schema.ts @@ -16,10 +16,7 @@ export const ListTokensSchema = z.object({ .optional() .describe('Sort tokens by created or modified timestamp'), start: z.string().optional().describe('Token ID to start pagination from'), - usage: z - .enum(['pk', 'sk', 'tk']) - .optional() - .describe('Filter by token type: pk (public), sk (secret), tk (temporary)') + usage: z.enum(['pk']).optional().describe('Filter by token type: pk (public)') }); export type ListTokensInput = z.infer; diff --git a/src/tools/list-tokens-tool/ListTokensTool.test.ts b/src/tools/list-tokens-tool/ListTokensTool.test.ts index e55f05a..582acff 100644 --- a/src/tools/list-tokens-tool/ListTokensTool.test.ts +++ b/src/tools/list-tokens-tool/ListTokensTool.test.ts @@ -61,7 +61,7 @@ describe('ListTokensTool', () => { it('validates usage enum values', async () => { const result = await tool.run({ - usage: 'invalid' as unknown as 'pk' | 'sk' | 'tk' + usage: 'invalid' as unknown as 'pk' }); expect(result.isError).toBe(true); @@ -217,8 +217,8 @@ describe('ListTokensTool', () => { { id: 'cktest789', note: 'Token 3', - usage: 'sk', - token: 'sk.eyJ1IjoidGVzdHVzZXIifQ.test789', + usage: 'pk', + token: 'pk.eyJ1IjoidGVzdHVzZXIifQ.test789', scopes: ['styles:read'], created: '2023-03-01T00:00:00.000Z', modified: '2023-03-01T00:00:00.000Z' @@ -260,8 +260,8 @@ describe('ListTokensTool', () => { { id: 'cktest789', note: 'Token 3', - usage: 'sk', - token: 'sk.eyJ1IjoidGVzdHVzZXIifQ.test789', + usage: 'pk', + token: 'pk.eyJ1IjoidGVzdHVzZXIifQ.test789', scopes: ['styles:read'], created: '2023-03-01T00:00:00.000Z', modified: '2023-03-01T00:00:00.000Z' @@ -294,8 +294,8 @@ describe('ListTokensTool', () => { { id: 'cktest789', note: 'Token 3', - usage: 'sk', - token: 'sk.eyJ1IjoidGVzdHVzZXIifQ.test789', + usage: 'pk', + token: 'pk.eyJ1IjoidGVzdHVzZXIifQ.test789', scopes: ['styles:read'], created: '2023-03-01T00:00:00.000Z', modified: '2023-03-01T00:00:00.000Z' @@ -328,8 +328,8 @@ describe('ListTokensTool', () => { { id: 'cktest789', note: 'Token 3', - usage: 'sk', - token: 'sk.eyJ1IjoidGVzdHVzZXIifQ.test789', + usage: 'pk', + token: 'pk.eyJ1IjoidGVzdHVzZXIifQ.test789', scopes: ['styles:read'], created: '2023-03-01T00:00:00.000Z', modified: '2023-03-01T00:00:00.000Z' @@ -372,8 +372,8 @@ describe('ListTokensTool', () => { { id: 'cktest789', note: 'Token 3', - usage: 'sk', - token: 'sk.eyJ1IjoidGVzdHVzZXIifQ.test789', + usage: 'pk', + token: 'pk.eyJ1IjoidGVzdHVzZXIifQ.test789', scopes: ['styles:read'], created: '2023-03-01T00:00:00.000Z', modified: '2023-03-01T00:00:00.000Z' @@ -398,14 +398,13 @@ describe('ListTokensTool', () => { it('filters by token usage type', async () => { const mockTokens = [ { - id: 'tktest123', - note: 'Temporary token', - usage: 'tk', - token: 'tk.eyJ1IjoidGVzdHVzZXIifQ.temp123', + id: 'pktest123', + note: 'Public token', + usage: 'pk', + token: 'pk.eyJ1IjoidGVzdHVzZXIifQ.pub123', scopes: ['styles:read'], created: '2023-04-01T00:00:00.000Z', - modified: '2023-04-01T00:00:00.000Z', - expires: '2023-04-01T01:00:00.000Z' + modified: '2023-04-01T00:00:00.000Z' } ]; @@ -416,16 +415,16 @@ describe('ListTokensTool', () => { json: async () => mockTokens } as Response); - const result = await tool.run({ usage: 'tk' }); + const result = await tool.run({ usage: 'pk' }); expect(result.isError).toBe(false); const responseData = JSON.parse((result.content[0] as TextContent).text); expect(responseData.tokens).toHaveLength(1); - expect(responseData.tokens[0].usage).toBe('tk'); + expect(responseData.tokens[0].usage).toBe('pk'); // Verify the usage parameter was included expect(fetchMock).toHaveBeenCalledWith( - expect.stringContaining('usage=tk'), + expect.stringContaining('usage=pk'), expect.any(Object) ); });