Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 7 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:**

Expand All @@ -130,31 +130,14 @@ 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
- `fonts:read` - Read fonts
- `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
Expand All @@ -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
Expand All @@ -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:**

Expand All @@ -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

Expand Down
Binary file modified assets/mcp_server_devkit.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion docs/claude-code-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion docs/claude-desktop-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
},
{
Expand Down
44 changes: 5 additions & 39 deletions src/tools/create-token-tool/CreateTokenTool.schema.ts
Original file line number Diff line number Diff line change
@@ -1,54 +1,20 @@
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',
'datasets:read',
'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())
Expand All @@ -65,4 +31,4 @@ export const CreateTokenSchema = z.object({
export type CreateTokenInput = z.infer<typeof CreateTokenSchema>;

// Export scopes for potential reuse
export { PUBLIC_SCOPES, SECRET_SCOPES, ALL_SCOPES };
export { SCOPES };
42 changes: 6 additions & 36 deletions src/tools/create-token-tool/CreateTokenTool.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.'
);
});

Expand Down Expand Up @@ -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',
Expand All @@ -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
});
Expand All @@ -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({
Expand All @@ -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);
Expand Down
24 changes: 3 additions & 21 deletions src/tools/create-token-tool/CreateTokenTool.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import { MapboxApiBasedTool } from '../MapboxApiBasedTool.js';
import {
CreateTokenSchema,
CreateTokenInput,
SECRET_SCOPES
CreateTokenInput
} from './CreateTokenTool.schema.js';

export class CreateTokenTool extends MapboxApiBasedTool<
typeof CreateTokenSchema
> {
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 });
Expand All @@ -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: {
Expand Down
5 changes: 1 addition & 4 deletions src/tools/list-tokens-tool/ListTokensTool.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof ListTokensSchema>;
Loading
Loading