Skip to content
Merged
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1304,6 +1304,29 @@ node dist/esm/index.js --disable-mcp-ui

**Note:** You typically don't need to disable this. The implementation is fully backwards compatible and doesn't affect clients that don't support MCP-UI. See [mcpui.dev](https://mcpui.dev) for compatible clients.

#### CLIENT_NEEDS_RESOURCE_FALLBACK

**Resource Fallback Tools (Opt-In for Non-Compliant Clients)**

Resources are a core MCP feature supported by most clients (Claude Desktop, VS Code, MCP Inspector, etc.). However, some clients (like smolagents) don't support resources at all. For these clients, the server can provide "resource fallback tools" that deliver the same content as resources but via tool calls.

**Fallback Tools:**

- `get_reference_tool` - Access to style layers, Streets v8 fields, token scopes, layer type mapping
- `get_latest_mapbox_docs_tool` - Access to Mapbox documentation

**By default, these tools are NOT included** (assumes your client supports resources). If your client doesn't support resources, enable the fallback tools:

```bash
export CLIENT_NEEDS_RESOURCE_FALLBACK=true
```

**When to set this:**

- ✅ Set to `true` if using smolagents or other clients without resource support
- ❌ Leave unset (default) if using Claude Desktop, VS Code, MCP Inspector, or any resource-capable client
- ❌ Leave unset if unsure (most clients support resources)

## Troubleshooting

**Issue:** Tools fail with authentication errors
Expand Down
99 changes: 93 additions & 6 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';
import { parseToolConfigFromArgs, filterTools } from './config/toolConfig.js';
import { getAllTools } from './tools/toolRegistry.js';
import {
getCoreTools,
getElicitationTools,
getResourceFallbackTools
} from './tools/toolRegistry.js';
import { getAllResources } from './resources/resourceRegistry.js';
import { getAllPrompts } from './prompts/promptRegistry.js';
import { getVersionInfo } from './utils/versionUtils.js';
Expand Down Expand Up @@ -54,8 +58,14 @@ const versionInfo = getVersionInfo();
const config = parseToolConfigFromArgs();

// Get and filter tools based on configuration
const allTools = getAllTools();
const enabledTools = filterTools(allTools, config);
// Split into categories for capability-aware registration
const coreTools = getCoreTools();
const elicitationTools = getElicitationTools();
const resourceFallbackTools = getResourceFallbackTools();

const enabledCoreTools = filterTools(coreTools, config);
const enabledElicitationTools = filterTools(elicitationTools, config);
const enabledResourceFallbackTools = filterTools(resourceFallbackTools, config);

// Create an MCP server
const server = new McpServer(
Expand All @@ -65,15 +75,18 @@ const server = new McpServer(
},
{
capabilities: {
tools: {},
tools: {
listChanged: true // Advertise support for dynamic tool registration
},
resources: {},
prompts: {}
}
}
);

// Register enabled tools to the server
enabledTools.forEach((tool) => {
// Register only core tools before connection
// Capability-dependent tools will be registered dynamically after connection
enabledCoreTools.forEach((tool) => {
tool.installTo(server);
});

Expand Down Expand Up @@ -210,6 +223,80 @@ async function main() {
// Start receiving messages on stdin and sending messages on stdout
const transport = new StdioServerTransport();
await server.connect(transport);

// After connection, dynamically register capability-dependent tools
const clientCapabilities = server.server.getClientCapabilities();

// Debug: Log what capabilities we detected
server.server.sendLoggingMessage({
level: 'info',
data: `Client capabilities detected: ${JSON.stringify(clientCapabilities, null, 2)}`
});

let toolsAdded = false;

// Register elicitation tools if client supports elicitation
if (clientCapabilities?.elicitation && enabledElicitationTools.length > 0) {
server.server.sendLoggingMessage({
level: 'info',
data: `Client supports elicitation. Registering ${enabledElicitationTools.length} elicitation-dependent tools`
});

enabledElicitationTools.forEach((tool) => {
tool.installTo(server);
});
toolsAdded = true;
} else if (enabledElicitationTools.length > 0) {
server.server.sendLoggingMessage({
level: 'debug',
data: `Client does not support elicitation. Skipping ${enabledElicitationTools.length} elicitation-dependent tools`
});
}

// Register resource fallback tools for clients that don't support resources
// Note: Resources are a core MCP feature supported by most clients.
// However, some clients (like smolagents) don't support resources at all.
// These fallback tools provide the same content as resources but via tool calls instead.
//
// Configuration via CLIENT_NEEDS_RESOURCE_FALLBACK environment variable:
// - unset (default) = Skip fallback tools (assume client supports resources)
// - "true" = Provide fallback tools (client does NOT support resources)
const clientNeedsResourceFallback =
process.env.CLIENT_NEEDS_RESOURCE_FALLBACK?.toLowerCase() === 'true';

if (clientNeedsResourceFallback && enabledResourceFallbackTools.length > 0) {
server.server.sendLoggingMessage({
level: 'info',
data: `CLIENT_NEEDS_RESOURCE_FALLBACK=true. Registering ${enabledResourceFallbackTools.length} resource fallback tools`
});

enabledResourceFallbackTools.forEach((tool) => {
tool.installTo(server);
});
toolsAdded = true;
} else if (enabledResourceFallbackTools.length > 0) {
server.server.sendLoggingMessage({
level: 'debug',
data: `CLIENT_NEEDS_RESOURCE_FALLBACK not set or false. Skipping ${enabledResourceFallbackTools.length} resource fallback tools (client supports resources)`
});
}

// Notify client about tool list changes if any tools were added
if (toolsAdded) {
try {
server.sendToolListChanged();

server.server.sendLoggingMessage({
level: 'debug',
data: 'Sent notifications/tools/list_changed to client'
});
} catch (error) {
server.server.sendLoggingMessage({
level: 'warning',
data: `Failed to send tool list change notification: ${error instanceof Error ? error.message : String(error)}`
});
}
}
}

// Ensure cleanup interval is cleared when the process exits
Expand Down
76 changes: 71 additions & 5 deletions src/tools/toolRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,11 @@ import { ValidateGeojsonTool } from './validate-geojson-tool/ValidateGeojsonTool
import { ValidateStyleTool } from './validate-style-tool/ValidateStyleTool.js';
import { httpRequest } from '../utils/httpPipeline.js';

// Central registry of all tools
export const ALL_TOOLS = [
/**
* Core tools that work in all MCP clients without requiring special capabilities
* These tools are registered immediately during server startup
*/
export const CORE_TOOLS = [
new ListStylesTool({ httpRequest }),
new CreateStyleTool({ httpRequest }),
new RetrieveStyleTool({ httpRequest }),
Expand All @@ -41,28 +44,91 @@ export const ALL_TOOLS = [
new CheckColorContrastTool(),
new CompareStylesTool(),
new OptimizeStyleTool(),
new StyleComparisonTool(),
new CreateTokenTool({ httpRequest }),
new ListTokensTool({ httpRequest }),
new BoundingBoxTool(),
new CountryBoundingBoxTool(),
new CoordinateConversionTool(),
new GetFeedbackTool({ httpRequest }),
new ListFeedbackTool({ httpRequest }),
new GetMapboxDocSourceTool({ httpRequest }),
new GetReferenceTool(),
new StyleComparisonTool(),
new TilequeryTool({ httpRequest }),
new ValidateExpressionTool(),
new ValidateGeojsonTool(),
new ValidateStyleTool()
] as const;

/**
* Tools that require elicitation capability for optimal functionality
* These tools use elicitInput() for secure token management
* Registered only if client supports elicitation
*
* Currently empty - elicitation support will be added in a future PR.
* This category is ready for tools that require the elicitation capability.
*/
export const ELICITATION_TOOLS = [] as const;

/**
* Tools that serve as bridges for clients without resource support
* These tools are only registered if CLIENT_NEEDS_RESOURCE_FALLBACK env var is set to "true"
*
* Context: Most MCP clients support resources (Claude Desktop, VS Code, Inspector, etc.).
* However, some clients (like smolagents) don't support resources at all.
* These tools provide the same content as resources but via tool calls instead.
*
* Configuration:
* - Leave unset (default) = Skip these tools (assumes client supports resources)
* - Set CLIENT_NEEDS_RESOURCE_FALLBACK=true = Include these tools (for smolagents, etc.)
*
* Tools:
* - GetReferenceTool: Provides access to reference resources (style layers, Streets v8 fields, token scopes, layer type mapping)
* - GetMapboxDocSourceTool: Provides access to Mapbox documentation (resource://mapbox-documentation)
*/
export const RESOURCE_FALLBACK_TOOLS = [
new GetReferenceTool(),
new GetMapboxDocSourceTool({ httpRequest })
] as const;

/**
* All tools combined (for backward compatibility and testing)
*/
export const ALL_TOOLS = [
...CORE_TOOLS,
...ELICITATION_TOOLS,
...RESOURCE_FALLBACK_TOOLS
] as const;

export type ToolInstance = (typeof ALL_TOOLS)[number];

/**
* Get all tools (for backward compatibility)
* @deprecated Use getCoreTools(), getElicitationTools(), etc. instead for capability-aware registration
*/
export function getAllTools(): readonly ToolInstance[] {
return ALL_TOOLS;
}

/**
* Get tools that work in all MCP clients
*/
export function getCoreTools(): readonly ToolInstance[] {
return CORE_TOOLS;
}

/**
* Get tools that require elicitation capability
*/
export function getElicitationTools(): readonly ToolInstance[] {
return ELICITATION_TOOLS;
}

/**
* Get tools that serve as fallbacks when client doesn't support resources
*/
export function getResourceFallbackTools(): readonly ToolInstance[] {
return RESOURCE_FALLBACK_TOOLS;
}

export function getToolByName(name: string): ToolInstance | undefined {
return ALL_TOOLS.find((tool) => tool.name === name);
}
Loading
Loading