From 8e6e4ad1b1669f4c7c1414ce113d2d8b3f7a4762 Mon Sep 17 00:00:00 2001 From: Matthew Podwysocki Date: Thu, 15 Jan 2026 13:25:17 -0500 Subject: [PATCH] Add resource://mapbox-documentation with proper MIME type support - Create MapboxDocumentationResource that fetches docs from docs.mapbox.com/llms.txt - Returns content with mimeType: text/markdown for proper typing - Update GetMapboxDocSourceTool description to mention the resource - Add comprehensive tests for the new resource - Fix TypeScript error in GetReferenceTool (text type assertion) Provides proper MIME type support for clients that handle resources well, while keeping the tool for broad compatibility. --- .../MapboxDocumentationResource.ts | 68 ++++++++++++ src/resources/resourceRegistry.ts | 5 +- .../GetMapboxDocSourceTool.ts | 2 +- .../get-reference-tool/GetReferenceTool.ts | 2 +- .../MapboxDocumentationResource.test.ts | 102 ++++++++++++++++++ .../tool-naming-convention.test.ts.snap | 2 +- 6 files changed, 177 insertions(+), 4 deletions(-) create mode 100644 src/resources/mapbox-documentation-resource/MapboxDocumentationResource.ts create mode 100644 test/resources/mapbox-documentation-resource/MapboxDocumentationResource.test.ts diff --git a/src/resources/mapbox-documentation-resource/MapboxDocumentationResource.ts b/src/resources/mapbox-documentation-resource/MapboxDocumentationResource.ts new file mode 100644 index 0000000..9fea819 --- /dev/null +++ b/src/resources/mapbox-documentation-resource/MapboxDocumentationResource.ts @@ -0,0 +1,68 @@ +// Copyright (c) Mapbox, Inc. +// Licensed under the MIT License. + +import type { RequestHandlerExtra } from '@modelcontextprotocol/sdk/shared/protocol.js'; +import type { + ReadResourceResult, + ServerNotification, + ServerRequest +} from '@modelcontextprotocol/sdk/types.js'; +import type { HttpRequest } from '../../utils/types.js'; +import { BaseResource } from '../BaseResource.js'; + +/** + * Resource providing the latest official Mapbox documentation + * fetched from docs.mapbox.com/llms.txt + */ +export class MapboxDocumentationResource extends BaseResource { + readonly name = 'Mapbox Documentation'; + readonly uri = 'resource://mapbox-documentation'; + readonly description = + 'Latest official Mapbox documentation, APIs, SDKs, and developer resources. Always up-to-date comprehensive coverage of all current Mapbox services.'; + readonly mimeType = 'text/markdown'; + + private httpRequest: HttpRequest; + + constructor(params: { httpRequest: HttpRequest }) { + super(); + this.httpRequest = params.httpRequest; + } + + public async readCallback( + uri: URL, + _extra: RequestHandlerExtra + ): Promise { + try { + const response = await this.httpRequest( + 'https://docs.mapbox.com/llms.txt', + { + headers: { + Accept: 'text/markdown, text/plain;q=0.9, */*;q=0.8' + } + } + ); + + if (!response.ok) { + throw new Error( + `Failed to fetch Mapbox documentation: ${response.statusText}` + ); + } + + const content = await response.text(); + + return { + contents: [ + { + uri: uri.href, + mimeType: this.mimeType, + text: content + } + ] + }; + } catch (error) { + const errorMessage = + error instanceof Error ? error.message : 'Unknown error occurred'; + throw new Error(`Failed to fetch Mapbox documentation: ${errorMessage}`); + } + } +} diff --git a/src/resources/resourceRegistry.ts b/src/resources/resourceRegistry.ts index a53c734..5288bd8 100644 --- a/src/resources/resourceRegistry.ts +++ b/src/resources/resourceRegistry.ts @@ -5,13 +5,16 @@ import { MapboxStyleLayersResource } from './mapbox-style-layers-resource/Mapbox import { MapboxStreetsV8FieldsResource } from './mapbox-streets-v8-fields-resource/MapboxStreetsV8FieldsResource.js'; import { MapboxTokenScopesResource } from './mapbox-token-scopes-resource/MapboxTokenScopesResource.js'; import { MapboxLayerTypeMappingResource } from './mapbox-layer-type-mapping-resource/MapboxLayerTypeMappingResource.js'; +import { MapboxDocumentationResource } from './mapbox-documentation-resource/MapboxDocumentationResource.js'; +import { httpRequest } from '../utils/httpPipeline.js'; // Central registry of all resources export const ALL_RESOURCES = [ new MapboxStyleLayersResource(), new MapboxStreetsV8FieldsResource(), new MapboxTokenScopesResource(), - new MapboxLayerTypeMappingResource() + new MapboxLayerTypeMappingResource(), + new MapboxDocumentationResource({ httpRequest }) ] as const; export type ResourceInstance = (typeof ALL_RESOURCES)[number]; diff --git a/src/tools/get-mapbox-doc-source-tool/GetMapboxDocSourceTool.ts b/src/tools/get-mapbox-doc-source-tool/GetMapboxDocSourceTool.ts index 03b8872..18715bf 100644 --- a/src/tools/get-mapbox-doc-source-tool/GetMapboxDocSourceTool.ts +++ b/src/tools/get-mapbox-doc-source-tool/GetMapboxDocSourceTool.ts @@ -14,7 +14,7 @@ export class GetMapboxDocSourceTool extends BaseTool< > { name = 'get_latest_mapbox_docs_tool'; description = - 'Get the latest official Mapbox documentation, APIs, SDKs, and developer resources directly from Mapbox. Always up-to-date, comprehensive coverage of all current Mapbox services including mapping, navigation, search, geocoding, and mobile SDKs. Use this for accurate, official Mapbox information instead of web search.'; + 'Get the latest official Mapbox documentation, APIs, SDKs, and developer resources directly from Mapbox. Always up-to-date, comprehensive coverage of all current Mapbox services including mapping, navigation, search, geocoding, and mobile SDKs. Use this for accurate, official Mapbox information instead of web search. For clients that support resources, use resource://mapbox-documentation for proper text/markdown MIME type support.'; readonly annotations = { readOnlyHint: true, destructiveHint: false, diff --git a/src/tools/get-reference-tool/GetReferenceTool.ts b/src/tools/get-reference-tool/GetReferenceTool.ts index 5bc8bf5..bfd01b9 100644 --- a/src/tools/get-reference-tool/GetReferenceTool.ts +++ b/src/tools/get-reference-tool/GetReferenceTool.ts @@ -69,7 +69,7 @@ export class GetReferenceTool extends BaseTool { content: [ { type: 'text', - text: result.contents[0].text + text: result.contents[0].text as string } ], isError: false diff --git a/test/resources/mapbox-documentation-resource/MapboxDocumentationResource.test.ts b/test/resources/mapbox-documentation-resource/MapboxDocumentationResource.test.ts new file mode 100644 index 0000000..73574f2 --- /dev/null +++ b/test/resources/mapbox-documentation-resource/MapboxDocumentationResource.test.ts @@ -0,0 +1,102 @@ +// Copyright (c) Mapbox, Inc. +// Licensed under the MIT License. + +import { describe, expect, it } from 'vitest'; +import { MapboxDocumentationResource } from '../../../src/resources/mapbox-documentation-resource/MapboxDocumentationResource.js'; +import { setupHttpRequest } from '../../utils/httpPipelineUtils.js'; + +describe('MapboxDocumentationResource', () => { + it('should have correct metadata', () => { + const { httpRequest } = setupHttpRequest(); + const resource = new MapboxDocumentationResource({ httpRequest }); + + expect(resource.name).toBe('Mapbox Documentation'); + expect(resource.uri).toBe('resource://mapbox-documentation'); + expect(resource.mimeType).toBe('text/markdown'); + expect(resource.description).toContain( + 'Latest official Mapbox documentation' + ); + }); + + it('should successfully fetch documentation content with proper MIME type', async () => { + const mockContent = `# Mapbox Documentation + +This is the Mapbox developer documentation for LLMs. + +## Web SDKs +- Mapbox GL JS for interactive maps +- Mobile SDKs for iOS and Android + +## APIs +- Geocoding API for address search +- Directions API for routing`; + + const { httpRequest, mockHttpRequest } = setupHttpRequest({ + ok: true, + status: 200, + text: () => Promise.resolve(mockContent) + }); + + const resource = new MapboxDocumentationResource({ httpRequest }); + const uri = new URL('resource://mapbox-documentation'); + const result = await resource.readCallback(uri, {} as any); + + expect(mockHttpRequest).toHaveBeenCalledWith( + 'https://docs.mapbox.com/llms.txt', + { + headers: { + Accept: 'text/markdown, text/plain;q=0.9, */*;q=0.8', + 'User-Agent': 'TestServer/1.0.0 (default, no-tag, abcdef)' + } + } + ); + + expect(result.contents).toHaveLength(1); + expect(result.contents[0]).toMatchObject({ + uri: 'resource://mapbox-documentation', + mimeType: 'text/markdown', + text: mockContent + }); + }); + + it('should handle HTTP errors', async () => { + const { httpRequest } = setupHttpRequest({ + ok: false, + status: 404, + statusText: 'Not Found' + }); + + const resource = new MapboxDocumentationResource({ httpRequest }); + const uri = new URL('resource://mapbox-documentation'); + + await expect(resource.readCallback(uri, {} as any)).rejects.toThrow( + 'Failed to fetch Mapbox documentation: Not Found' + ); + }); + + it('should handle network errors', async () => { + const { httpRequest } = setupHttpRequest({ + text: () => Promise.reject(new Error('Network error')) + }); + + const resource = new MapboxDocumentationResource({ httpRequest }); + const uri = new URL('resource://mapbox-documentation'); + + await expect(resource.readCallback(uri, {} as any)).rejects.toThrow( + 'Failed to fetch Mapbox documentation: Network error' + ); + }); + + it('should handle unknown errors', async () => { + const { httpRequest } = setupHttpRequest({ + text: () => Promise.reject('String error') + }); + + const resource = new MapboxDocumentationResource({ httpRequest }); + const uri = new URL('resource://mapbox-documentation'); + + await expect(resource.readCallback(uri, {} as any)).rejects.toThrow( + 'Failed to fetch Mapbox documentation: Unknown error occurred' + ); + }); +}); diff --git a/test/tools/__snapshots__/tool-naming-convention.test.ts.snap b/test/tools/__snapshots__/tool-naming-convention.test.ts.snap index 9e24fd1..5b517de 100644 --- a/test/tools/__snapshots__/tool-naming-convention.test.ts.snap +++ b/test/tools/__snapshots__/tool-naming-convention.test.ts.snap @@ -54,7 +54,7 @@ exports[`Tool Naming Convention > should maintain consistent tool list (snapshot }, { "className": "GetMapboxDocSourceTool", - "description": "Get the latest official Mapbox documentation, APIs, SDKs, and developer resources directly from Mapbox. Always up-to-date, comprehensive coverage of all current Mapbox services including mapping, navigation, search, geocoding, and mobile SDKs. Use this for accurate, official Mapbox information instead of web search.", + "description": "Get the latest official Mapbox documentation, APIs, SDKs, and developer resources directly from Mapbox. Always up-to-date, comprehensive coverage of all current Mapbox services including mapping, navigation, search, geocoding, and mobile SDKs. Use this for accurate, official Mapbox information instead of web search. For clients that support resources, use resource://mapbox-documentation for proper text/markdown MIME type support.", "toolName": "get_latest_mapbox_docs_tool", }, {