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", }, {