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
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
## 0.4.7

### Security

- **CVE-2026-0621**: Updated `@modelcontextprotocol/sdk` to 1.25.3 to fix ReDoS vulnerability in UriTemplate regex patterns

### Bug Fixes

- Migrated from deprecated `server.resource()` to `server.registerResource()` API in BaseResource
- Fixed TypeScript implicit `any` type error in BaseTool registerTool callback

### Dependencies

- Updated `@modelcontextprotocol/sdk` from 1.17.5 to 1.25.3

## 0.4.6

### Features Added
Expand Down
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"dxt_version": "0.1",
"name": "@mapbox/mcp-devkit-server",
"display_name": "Mapbox MCP DevKit Server",
"version": "0.4.6",
"version": "0.4.7",
"description": "Mapbox MCP devkit server",
"author": {
"name": "Mapbox, Inc."
Expand Down
1,074 changes: 747 additions & 327 deletions package-lock.json

Large diffs are not rendered by default.

11 changes: 7 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@mapbox/mcp-devkit-server",
"version": "0.4.6",
"version": "0.4.7",
"description": "Mapbox MCP devkit server",
"mcpName": "io.github.mapbox/mcp-devkit-server",
"main": "./dist/commonjs/index.js",
Expand All @@ -10,6 +10,7 @@
"mapbox-mcp-devkit": "dist/esm/index.js"
},
"scripts": {
"postinstall": "patch-package || true",
"build": "npm run prepare && tshy && npm run generate-version && node scripts/add-shebang.cjs",
"format": "prettier --check \"./src/**/*.{ts,tsx,js,json,md}\" \"./test/**/*.{ts,tsx,js,json,md}\"",
"format:fix": "prettier --write \"./src/**/*.{ts,tsx,js,json,md}\" \"./test/**/*.{ts,tsx,js,json,md}\"",
Expand Down Expand Up @@ -40,14 +41,15 @@
"node": ">=22"
},
"files": [
"dist"
"dist",
"patches"
],
"keywords": [
"mcp"
],
"dependencies": {
"@mcp-ui/server": "^5.13.1",
"@modelcontextprotocol/sdk": "^1.17.5",
"@modelcontextprotocol/sdk": "^1.25.3",
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/auto-instrumentations-node": "^0.56.0",
"@opentelemetry/exporter-trace-otlp-http": "^0.56.0",
Expand Down Expand Up @@ -79,7 +81,8 @@
"tshy": "^3.0.2",
"typescript": "^5.8.3",
"typescript-eslint": "^8.42.0",
"vitest": "^3.2.4"
"vitest": "^3.2.4",
"patch-package": "^8.0.1"
},
"prettier": {
"singleQuote": true,
Expand Down
66 changes: 66 additions & 0 deletions patches/@modelcontextprotocol+sdk+1.25.3.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
diff --git a/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/mcp.js b/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/mcp.js
index e10bb3d..2e99bee 100644
--- a/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/mcp.js
+++ b/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/mcp.js
@@ -197,15 +197,20 @@ class McpServer {
return;
}
if (!result.structuredContent) {
- throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, `Output validation error: Tool ${toolName} has an output schema but no structured content was provided`);
+ // Log warning but don't throw - allow tools to omit structured content
+ console.warn(`[MCP SDK Patch] Output validation warning: Tool ${toolName} has an output schema but no structured content was provided`);
}
- // if the tool has an output schema, validate structured content
- const outputObj = (0, zod_compat_js_1.normalizeObjectSchema)(tool.outputSchema);
- const parseResult = await (0, zod_compat_js_1.safeParseAsync)(outputObj, result.structuredContent);
- if (!parseResult.success) {
- const error = 'error' in parseResult ? parseResult.error : 'Unknown error';
- const errorMessage = (0, zod_compat_js_1.getParseErrorMessage)(error);
- throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, `Output validation error: Invalid structured content for tool ${toolName}: ${errorMessage}`);
+ else {
+ // if the tool has an output schema, validate structured content
+ const outputObj = (0, zod_compat_js_1.normalizeObjectSchema)(tool.outputSchema);
+ const parseResult = await (0, zod_compat_js_1.safeParseAsync)(outputObj, result.structuredContent);
+ if (!parseResult.success) {
+ const error = 'error' in parseResult ? parseResult.error : 'Unknown error';
+ const errorMessage = (0, zod_compat_js_1.getParseErrorMessage)(error);
+ // Log warning but don't throw - allow schema mismatches
+ console.warn(`[MCP SDK Patch] Output validation warning: Invalid structured content for tool ${toolName}: ${errorMessage}`);
+ // Keep the structuredContent despite validation failure
+ }
}
}
/**
diff --git a/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js b/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js
index 23639ce..7b8a325 100644
--- a/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js
+++ b/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js
@@ -194,15 +194,20 @@ export class McpServer {
return;
}
if (!result.structuredContent) {
- throw new McpError(ErrorCode.InvalidParams, `Output validation error: Tool ${toolName} has an output schema but no structured content was provided`);
+ // Log warning but don't throw - allow tools to omit structured content
+ console.warn(`[MCP SDK Patch] Output validation warning: Tool ${toolName} has an output schema but no structured content was provided`);
}
- // if the tool has an output schema, validate structured content
- const outputObj = normalizeObjectSchema(tool.outputSchema);
- const parseResult = await safeParseAsync(outputObj, result.structuredContent);
- if (!parseResult.success) {
- const error = 'error' in parseResult ? parseResult.error : 'Unknown error';
- const errorMessage = getParseErrorMessage(error);
- throw new McpError(ErrorCode.InvalidParams, `Output validation error: Invalid structured content for tool ${toolName}: ${errorMessage}`);
+ else {
+ // if the tool has an output schema, validate structured content
+ const outputObj = normalizeObjectSchema(tool.outputSchema);
+ const parseResult = await safeParseAsync(outputObj, result.structuredContent);
+ if (!parseResult.success) {
+ const error = 'error' in parseResult ? parseResult.error : 'Unknown error';
+ const errorMessage = getParseErrorMessage(error);
+ // Log warning but don't throw - allow schema mismatches
+ console.warn(`[MCP SDK Patch] Output validation warning: Invalid structured content for tool ${toolName}: ${errorMessage}`);
+ // Keep the structuredContent despite validation failure
+ }
}
}
/**
41 changes: 41 additions & 0 deletions patches/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# MCP SDK Patches

This directory contains patches for npm dependencies that are automatically applied after `npm install`.

## @modelcontextprotocol/sdk

**File:** `@modelcontextprotocol+sdk+1.21.1.patch`

**Purpose:** Makes MCP output schema validation non-fatal to improve resilience and user experience.

**Problem:** The MCP SDK enforces strict output schema validation, which causes the entire tool call to fail when there are minor schema mismatches. This creates unnecessary risk and poor user experience - most MCP clients can gracefully handle responses that don't perfectly match the declared schema.

**Solution:** This patch modifies the MCP SDK to log warnings instead of throwing errors when output validation fails. This provides a better balance between:

- **Schema documentation** - Output schemas still serve as documentation for expected response structure
- **Resilience** - Tools continue working even with minor variations in response format
- **Observability** - Validation issues are logged for monitoring and debugging
- **User experience** - Clients receive the actual data instead of an error

**Benefits:**

- Prevents tool failures due to minor schema variations
- Allows graceful degradation when APIs evolve or return edge cases
- Maintains backward compatibility while improving reliability
- Clients that need strict validation can implement it themselves

**Changes:**

- Converts validation errors to console warnings with `[MCP SDK Patch]` prefix
- Allows tools to return structured content even when it doesn't match the schema exactly
- Preserves all existing functionality while removing unnecessary strictness

**Maintenance:**

- This patch is automatically applied after `npm install` via the `postinstall` script
- If the MCP SDK is updated, you may need to recreate this patch:
1. Remove the old patch file
2. Make the same modifications to the new SDK version
3. Run `npx patch-package @modelcontextprotocol/sdk`

**Philosophy:** This patch follows the robustness principle: "Be conservative in what you send, be liberal in what you accept." Output schemas remain valuable for documentation and tooling, but shouldn't cause failures when the real-world data varies slightly from the ideal schema.
6 changes: 3 additions & 3 deletions server.json
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json",
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "io.github.mapbox/mcp-devkit-server",
"description": "Provides AI assistants with direct access to Mapbox developer APIs and documentation.",
"repository": {
"url": "https://github.com/mapbox/mcp-devkit-server",
"source": "github"
},
"version": "0.4.6",
"version": "0.4.7",
"packages": [
{
"registryType": "npm",
"registryBaseUrl": "https://registry.npmjs.org",
"runtimeHint": "npx",
"version": "0.4.6",
"version": "0.4.7",
"identifier": "@mapbox/mcp-devkit-server",
"transport": {
"type": "stdio"
Expand Down
2 changes: 1 addition & 1 deletion src/resources/BaseResource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export abstract class BaseResource {
* Install this resource to the MCP server
*/
installTo(server: McpServer): void {
server.resource(
server.registerResource(
this.name,
this.uri,
{
Expand Down
7 changes: 5 additions & 2 deletions src/tools/BaseTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,11 @@ export abstract class BaseTool<
(this.outputSchema as unknown as z.ZodObject<any>).shape;
}

return server.registerTool(this.name, config, (args, extra) =>
this.run(args, extra)
return server.registerTool(
this.name,
config,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(args: any, extra: any) => this.run(args, extra)
);
}

Expand Down
56 changes: 27 additions & 29 deletions src/tools/create-style-tool/CreateStyleTool.output.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,35 @@
// Licensed under the MIT License.

import { z } from 'zod';
import { BaseStylePropertiesSchema } from '../../schemas/style.js';

// OUTPUT Schema - Simplified schema for tool responses
// This schema describes the key metadata fields returned, not the entire style spec.
// The full style data is included in structuredContent but uses .passthrough()
// to avoid overwhelming clients with a massive schema definition.
export const MapboxStyleOutputSchema = z
.object({
// API-specific metadata properties
id: z.string().describe('Unique style identifier'),
name: z.string().describe('Human-readable name for the style'),
owner: z.string().describe('Username of the style owner'),
created: z
.string()
.datetime()
.describe('ISO 8601 timestamp when style was created'),
modified: z
.string()
.datetime()
.describe('ISO 8601 timestamp when style was last modified'),
visibility: z
.enum(['public', 'private'])
.describe('Style visibility setting'),
draft: z.boolean().optional().describe('Whether this is a draft version'),
// OUTPUT Schema - For API responses (POST response)
// Uses the same comprehensive schema as RetrieveStyleTool to ensure all
// properties returned by the Mapbox API are explicitly defined.
// This avoids issues with additionalProperties: false in strict validators.
export const MapboxStyleOutputSchema = BaseStylePropertiesSchema.extend({
name: z.string().describe('Human-readable name for the style'),

// Style spec version (always 8)
version: z.literal(8).describe('Style specification version number')
})
.passthrough()
.describe(
'Mapbox style with metadata. Additional style properties (sources, layers, etc.) are included but not explicitly validated to keep the schema manageable.'
);
// API-specific properties (only present in responses)
id: z.string().describe('Unique style identifier'),
owner: z.string().describe('Username of the style owner'),
created: z
.string()
.datetime()
.describe('ISO 8601 timestamp when style was created'),
modified: z
.string()
.datetime()
.describe('ISO 8601 timestamp when style was last modified'),
visibility: z
.enum(['public', 'private'])
.describe('Style visibility setting'),
protected: z
.boolean()
.optional()
.describe('Whether style is protected from modifications'),
draft: z.boolean().optional().describe('Whether this is a draft version')
});

// Type exports
export type MapboxStyleOutput = z.infer<typeof MapboxStyleOutputSchema>;
16 changes: 15 additions & 1 deletion src/tools/get-reference-tool/GetReferenceTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,25 @@ export class GetReferenceTool extends BaseTool<typeof GetReferenceSchema> {
};
}

const content = result.contents[0];
// Type guard: check if content has 'text' property (vs 'blob' for binary resources)
if (!('text' in content)) {
return {
content: [
{
type: 'text',
text: `Reference ${input.reference} returned binary content, expected text`
}
],
isError: true
};
}

return {
content: [
{
type: 'text',
text: result.contents[0].text as string
text: content.text
}
],
isError: false
Expand Down
4 changes: 3 additions & 1 deletion src/tools/tilequery-tool/TilequeryTool.output.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ const CoordinatesSchema = z.tuple([z.number(), z.number()]);
// Vector Tileset Feature Schema
const VectorTilequeryFeatureSchema = z.object({
type: z.literal('Feature'),
id: z.string().describe('Feature identifier'),
id: z
.union([z.string(), z.number()])
.describe('Feature identifier (string or number per GeoJSON spec)'),
geometry: z.object({
type: z.literal('Point'),
coordinates: CoordinatesSchema
Expand Down
56 changes: 27 additions & 29 deletions src/tools/update-style-tool/UpdateStyleTool.output.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,35 @@
// Licensed under the MIT License.

import { z } from 'zod';
import { BaseStylePropertiesSchema } from '../../schemas/style.js';

// OUTPUT Schema - Simplified schema for tool responses
// This schema describes the key metadata fields returned, not the entire style spec.
// The full style data is included in structuredContent but uses .passthrough()
// to avoid overwhelming clients with a massive schema definition.
export const MapboxStyleOutputSchema = z
.object({
// API-specific metadata properties
id: z.string().describe('Unique style identifier'),
name: z.string().describe('Human-readable name for the style'),
owner: z.string().describe('Username of the style owner'),
created: z
.string()
.datetime()
.describe('ISO 8601 timestamp when style was created'),
modified: z
.string()
.datetime()
.describe('ISO 8601 timestamp when style was last modified'),
visibility: z
.enum(['public', 'private'])
.describe('Style visibility setting'),
draft: z.boolean().optional().describe('Whether this is a draft version'),
// OUTPUT Schema - For API responses (PATCH response)
// Uses the same comprehensive schema as RetrieveStyleTool to ensure all
// properties returned by the Mapbox API are explicitly defined.
// This avoids issues with additionalProperties: false in strict validators.
export const MapboxStyleOutputSchema = BaseStylePropertiesSchema.extend({
name: z.string().describe('Human-readable name for the style'),

// Style spec version (always 8)
version: z.literal(8).describe('Style specification version number')
})
.passthrough()
.describe(
'Mapbox style with metadata. Additional style properties (sources, layers, etc.) are included but not explicitly validated to keep the schema manageable.'
);
// API-specific properties (only present in responses)
id: z.string().describe('Unique style identifier'),
owner: z.string().describe('Username of the style owner'),
created: z
.string()
.datetime()
.describe('ISO 8601 timestamp when style was created'),
modified: z
.string()
.datetime()
.describe('ISO 8601 timestamp when style was last modified'),
visibility: z
.enum(['public', 'private'])
.describe('Style visibility setting'),
protected: z
.boolean()
.optional()
.describe('Whether style is protected from modifications'),
draft: z.boolean().optional().describe('Whether this is a draft version')
});

// Type exports
export type MapboxStyleOutput = z.infer<typeof MapboxStyleOutputSchema>;
Loading