Skip to content

Commit 45fa1b3

Browse files
Add resource://mapbox-documentation with proper MIME type support (#59)
- 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.
1 parent f46aed9 commit 45fa1b3

6 files changed

Lines changed: 177 additions & 4 deletions

File tree

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// Copyright (c) Mapbox, Inc.
2+
// Licensed under the MIT License.
3+
4+
import type { RequestHandlerExtra } from '@modelcontextprotocol/sdk/shared/protocol.js';
5+
import type {
6+
ReadResourceResult,
7+
ServerNotification,
8+
ServerRequest
9+
} from '@modelcontextprotocol/sdk/types.js';
10+
import type { HttpRequest } from '../../utils/types.js';
11+
import { BaseResource } from '../BaseResource.js';
12+
13+
/**
14+
* Resource providing the latest official Mapbox documentation
15+
* fetched from docs.mapbox.com/llms.txt
16+
*/
17+
export class MapboxDocumentationResource extends BaseResource {
18+
readonly name = 'Mapbox Documentation';
19+
readonly uri = 'resource://mapbox-documentation';
20+
readonly description =
21+
'Latest official Mapbox documentation, APIs, SDKs, and developer resources. Always up-to-date comprehensive coverage of all current Mapbox services.';
22+
readonly mimeType = 'text/markdown';
23+
24+
private httpRequest: HttpRequest;
25+
26+
constructor(params: { httpRequest: HttpRequest }) {
27+
super();
28+
this.httpRequest = params.httpRequest;
29+
}
30+
31+
public async readCallback(
32+
uri: URL,
33+
_extra: RequestHandlerExtra<ServerRequest, ServerNotification>
34+
): Promise<ReadResourceResult> {
35+
try {
36+
const response = await this.httpRequest(
37+
'https://docs.mapbox.com/llms.txt',
38+
{
39+
headers: {
40+
Accept: 'text/markdown, text/plain;q=0.9, */*;q=0.8'
41+
}
42+
}
43+
);
44+
45+
if (!response.ok) {
46+
throw new Error(
47+
`Failed to fetch Mapbox documentation: ${response.statusText}`
48+
);
49+
}
50+
51+
const content = await response.text();
52+
53+
return {
54+
contents: [
55+
{
56+
uri: uri.href,
57+
mimeType: this.mimeType,
58+
text: content
59+
}
60+
]
61+
};
62+
} catch (error) {
63+
const errorMessage =
64+
error instanceof Error ? error.message : 'Unknown error occurred';
65+
throw new Error(`Failed to fetch Mapbox documentation: ${errorMessage}`);
66+
}
67+
}
68+
}

src/resources/resourceRegistry.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,16 @@ import { MapboxStyleLayersResource } from './mapbox-style-layers-resource/Mapbox
55
import { MapboxStreetsV8FieldsResource } from './mapbox-streets-v8-fields-resource/MapboxStreetsV8FieldsResource.js';
66
import { MapboxTokenScopesResource } from './mapbox-token-scopes-resource/MapboxTokenScopesResource.js';
77
import { MapboxLayerTypeMappingResource } from './mapbox-layer-type-mapping-resource/MapboxLayerTypeMappingResource.js';
8+
import { MapboxDocumentationResource } from './mapbox-documentation-resource/MapboxDocumentationResource.js';
9+
import { httpRequest } from '../utils/httpPipeline.js';
810

911
// Central registry of all resources
1012
export const ALL_RESOURCES = [
1113
new MapboxStyleLayersResource(),
1214
new MapboxStreetsV8FieldsResource(),
1315
new MapboxTokenScopesResource(),
14-
new MapboxLayerTypeMappingResource()
16+
new MapboxLayerTypeMappingResource(),
17+
new MapboxDocumentationResource({ httpRequest })
1518
] as const;
1619

1720
export type ResourceInstance = (typeof ALL_RESOURCES)[number];

src/tools/get-mapbox-doc-source-tool/GetMapboxDocSourceTool.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export class GetMapboxDocSourceTool extends BaseTool<
1414
> {
1515
name = 'get_latest_mapbox_docs_tool';
1616
description =
17-
'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.';
17+
'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.';
1818
readonly annotations = {
1919
readOnlyHint: true,
2020
destructiveHint: false,

src/tools/get-reference-tool/GetReferenceTool.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ export class GetReferenceTool extends BaseTool<typeof GetReferenceSchema> {
6969
content: [
7070
{
7171
type: 'text',
72-
text: result.contents[0].text
72+
text: result.contents[0].text as string
7373
}
7474
],
7575
isError: false
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// Copyright (c) Mapbox, Inc.
2+
// Licensed under the MIT License.
3+
4+
import { describe, expect, it } from 'vitest';
5+
import { MapboxDocumentationResource } from '../../../src/resources/mapbox-documentation-resource/MapboxDocumentationResource.js';
6+
import { setupHttpRequest } from '../../utils/httpPipelineUtils.js';
7+
8+
describe('MapboxDocumentationResource', () => {
9+
it('should have correct metadata', () => {
10+
const { httpRequest } = setupHttpRequest();
11+
const resource = new MapboxDocumentationResource({ httpRequest });
12+
13+
expect(resource.name).toBe('Mapbox Documentation');
14+
expect(resource.uri).toBe('resource://mapbox-documentation');
15+
expect(resource.mimeType).toBe('text/markdown');
16+
expect(resource.description).toContain(
17+
'Latest official Mapbox documentation'
18+
);
19+
});
20+
21+
it('should successfully fetch documentation content with proper MIME type', async () => {
22+
const mockContent = `# Mapbox Documentation
23+
24+
This is the Mapbox developer documentation for LLMs.
25+
26+
## Web SDKs
27+
- Mapbox GL JS for interactive maps
28+
- Mobile SDKs for iOS and Android
29+
30+
## APIs
31+
- Geocoding API for address search
32+
- Directions API for routing`;
33+
34+
const { httpRequest, mockHttpRequest } = setupHttpRequest({
35+
ok: true,
36+
status: 200,
37+
text: () => Promise.resolve(mockContent)
38+
});
39+
40+
const resource = new MapboxDocumentationResource({ httpRequest });
41+
const uri = new URL('resource://mapbox-documentation');
42+
const result = await resource.readCallback(uri, {} as any);
43+
44+
expect(mockHttpRequest).toHaveBeenCalledWith(
45+
'https://docs.mapbox.com/llms.txt',
46+
{
47+
headers: {
48+
Accept: 'text/markdown, text/plain;q=0.9, */*;q=0.8',
49+
'User-Agent': 'TestServer/1.0.0 (default, no-tag, abcdef)'
50+
}
51+
}
52+
);
53+
54+
expect(result.contents).toHaveLength(1);
55+
expect(result.contents[0]).toMatchObject({
56+
uri: 'resource://mapbox-documentation',
57+
mimeType: 'text/markdown',
58+
text: mockContent
59+
});
60+
});
61+
62+
it('should handle HTTP errors', async () => {
63+
const { httpRequest } = setupHttpRequest({
64+
ok: false,
65+
status: 404,
66+
statusText: 'Not Found'
67+
});
68+
69+
const resource = new MapboxDocumentationResource({ httpRequest });
70+
const uri = new URL('resource://mapbox-documentation');
71+
72+
await expect(resource.readCallback(uri, {} as any)).rejects.toThrow(
73+
'Failed to fetch Mapbox documentation: Not Found'
74+
);
75+
});
76+
77+
it('should handle network errors', async () => {
78+
const { httpRequest } = setupHttpRequest({
79+
text: () => Promise.reject(new Error('Network error'))
80+
});
81+
82+
const resource = new MapboxDocumentationResource({ httpRequest });
83+
const uri = new URL('resource://mapbox-documentation');
84+
85+
await expect(resource.readCallback(uri, {} as any)).rejects.toThrow(
86+
'Failed to fetch Mapbox documentation: Network error'
87+
);
88+
});
89+
90+
it('should handle unknown errors', async () => {
91+
const { httpRequest } = setupHttpRequest({
92+
text: () => Promise.reject('String error')
93+
});
94+
95+
const resource = new MapboxDocumentationResource({ httpRequest });
96+
const uri = new URL('resource://mapbox-documentation');
97+
98+
await expect(resource.readCallback(uri, {} as any)).rejects.toThrow(
99+
'Failed to fetch Mapbox documentation: Unknown error occurred'
100+
);
101+
});
102+
});

test/tools/__snapshots__/tool-naming-convention.test.ts.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ exports[`Tool Naming Convention > should maintain consistent tool list (snapshot
5454
},
5555
{
5656
"className": "GetMapboxDocSourceTool",
57-
"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.",
57+
"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.",
5858
"toolName": "get_latest_mapbox_docs_tool",
5959
},
6060
{

0 commit comments

Comments
 (0)