An MCP server providing AI assistants with access to Mapbox documentation and reference materials. No Mapbox access token required for most tools.
- Runtime: Node.js 22+
- Language: TypeScript (strict mode)
- Build:
tshy(dual ESM/CJS output) - Testing: Vitest
- Package Manager: npm
src/
├── index.ts # MCP server entry point
├── config/toolConfig.ts # CLI argument parser (--enable-tools, --disable-tools)
├── constants/ # Static data (API endpoint definitions, etc.)
├── resources/ # MCP resource implementations
│ ├── BaseResource.ts # Abstract base class for resources
│ ├── resourceRegistry.ts # Resource registration
│ ├── utils/docParser.ts # Shared doc parsing utilities
│ └── */ # Individual resource implementations
├── tools/ # MCP tool implementations
│ ├── BaseTool.ts # Abstract base class for tools
│ ├── toolRegistry.ts # Tool registration
│ └── */ # Individual tool implementations
└── utils/
├── httpPipeline.ts # HTTP pipeline with User-Agent, caching, retry
├── types.ts # Shared types (HttpRequest, etc.)
└── versionUtils.ts # Version info
test/ # Mirrors src/ structure
All tools extend BaseTool<InputSchema, OutputSchema>:
export class MyTool extends BaseTool<
typeof MyInputSchema,
typeof MyOutputSchema
> {
readonly name = 'my_tool';
readonly description = '...';
readonly annotations = { title: 'My Tool', readOnlyHint: true, ... };
constructor({ httpRequest }: { httpRequest: HttpRequest }) {
super({ inputSchema: MyInputSchema, outputSchema: MyOutputSchema });
this.httpRequest = httpRequest;
}
protected async execute(
input: z.infer<typeof MyInputSchema>
): Promise<CallToolResult> {
// no accessToken param — docs tools don't need one
}
}execute(input)takes only the validated input — noaccessTokenorcontextparams- If a tool needs a token (e.g.
test_api_request_tool), add it as a field in the input schema - Register new tools in
src/tools/toolRegistry.tsCORE_TOOLSarray
- Never patch
global.fetch— always use the injectedhttpRequest - The shared
httpRequestfromhttpPipeline.tsadds User-Agent headers, 1-hour caching, and retry logic - Pass
httpRequestto tool/resource constructors for testability
All resources extend BaseResource:
export class MyResource extends BaseResource {
readonly name = 'My Resource';
readonly uri = 'resource://my-resource';
readonly description = '...';
readonly mimeType = 'text/markdown';
public async readCallback(uri: URL, _extra): Promise<ReadResourceResult> {
// fetch and return content
}
}Register in src/resources/resourceRegistry.ts.
npm install # Install dependencies
npm test # Run tests (vitest)
npm run build # Compile TypeScript (ESM + CJS via tshy)
npm run lint # ESLint
npm run lint:fix # ESLint with auto-fix
npm run format # Prettier check
npm run format:fix # Prettier auto-fix
npm run inspect:build # MCP Inspector against built server
npm run inspect:dev # MCP Inspector against source (tsx, no build needed)- Create
src/tools/my-tool/MyTool.input.schema.ts— Zod input schema - Create
src/tools/my-tool/MyTool.output.schema.ts— Zod output schema (if usingstructuredContent) - Create
src/tools/my-tool/MyTool.ts— extendsBaseTool - Register in
src/tools/toolRegistry.ts - Create
test/tools/my-tool/MyTool.test.ts— mock all HTTP calls
- Always update
CHANGELOG.md— add entry underUnreleasedwith PR number - All tests must pass:
npm test - Lint and format must pass:
npm run lint && npm run format
- No global patching: use
httpRequestfrom the pipeline - Dependency injection: tools and resources receive
httpRequestin constructor - No real network calls in tests: mock
httpRequestwithvi.fn() - Strict types: avoid
any - No access token env var: docs tools are token-free; if a token is needed, it's an input field