Skip to content

Commit 86a2908

Browse files
[mcp-ui] Adding MCP-UI support to those that support URLs
1 parent 2817d8f commit 86a2908

File tree

5 files changed

+89
-15
lines changed

5 files changed

+89
-15
lines changed

src/tools/geojson-preview-tool/GeojsonPreviewTool.ts

Lines changed: 63 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,36 @@ export class GeojsonPreviewTool extends BaseTool<typeof GeojsonPreviewSchema> {
5252
);
5353
}
5454

55+
/**
56+
* Generate a Mapbox Static Images API URL for the GeoJSON data
57+
* @see https://docs.mapbox.com/api/maps/static-images/
58+
*/
59+
private generateStaticImageUrl(geojsonData: GeoJSON): string | null {
60+
const accessToken = process.env.MAPBOX_ACCESS_TOKEN;
61+
if (!accessToken) {
62+
return null; // Fallback to geojson.io if no token available
63+
}
64+
65+
// Create a simplified GeoJSON for the overlay
66+
// The Static API requires specific format for GeoJSON overlays
67+
const geojsonString = JSON.stringify(geojsonData);
68+
const encodedGeoJSON = encodeURIComponent(geojsonString);
69+
70+
// Use Mapbox Streets style with auto-bounds fitting
71+
// Format: /styles/v1/{username}/{style_id}/static/geojson({geojson})/auto/{width}x{height}@2x
72+
const staticImageUrl =
73+
`https://api.mapbox.com/styles/v1/mapbox/streets-v12/static/` +
74+
`geojson(${encodedGeoJSON})/auto/1000x700@2x` +
75+
`?access_token=${accessToken}`;
76+
77+
// Check if URL is too long (browsers typically limit to ~8192 chars)
78+
if (staticImageUrl.length > 8000) {
79+
return null; // Fallback to geojson.io for large GeoJSON
80+
}
81+
82+
return staticImageUrl;
83+
}
84+
5585
protected async execute(input: GeojsonPreviewInput): Promise<CallToolResult> {
5686
try {
5787
// Parse and validate JSON format
@@ -92,15 +122,39 @@ export class GeojsonPreviewTool extends BaseTool<typeof GeojsonPreviewSchema> {
92122
.digest('hex')
93123
.substring(0, 16); // Use first 16 chars for brevity
94124

95-
const uiResource = createUIResource({
96-
uri: `ui://mapbox/geojson-preview/${contentHash}`,
97-
content: {
98-
type: 'externalUrl',
99-
iframeUrl: geojsonIOUrl
100-
},
101-
encoding: 'text'
102-
});
103-
content.push(uiResource);
125+
// Try to generate a Mapbox Static Image URL
126+
const staticImageUrl = this.generateStaticImageUrl(geojsonData);
127+
128+
if (staticImageUrl) {
129+
// Use Mapbox Static Images API - embeds as an image
130+
const uiResource = createUIResource({
131+
uri: `ui://mapbox/geojson-preview/${contentHash}`,
132+
content: {
133+
type: 'externalUrl',
134+
iframeUrl: staticImageUrl
135+
},
136+
encoding: 'text',
137+
uiMetadata: {
138+
'preferred-frame-size': ['1000px', '700px']
139+
}
140+
});
141+
content.push(uiResource);
142+
} else {
143+
// Fallback to geojson.io URL (for large GeoJSON or when no token)
144+
// Note: geojson.io may not work in iframes due to X-Frame-Options
145+
const uiResource = createUIResource({
146+
uri: `ui://mapbox/geojson-preview/${contentHash}`,
147+
content: {
148+
type: 'externalUrl',
149+
iframeUrl: geojsonIOUrl
150+
},
151+
encoding: 'text',
152+
uiMetadata: {
153+
'preferred-frame-size': ['1000px', '700px']
154+
}
155+
});
156+
content.push(uiResource);
157+
}
104158
}
105159

106160
return {

src/tools/preview-style-tool/PreviewStyleTool.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,10 @@ export class PreviewStyleTool extends BaseTool<typeof PreviewStyleSchema> {
8181
type: 'externalUrl',
8282
iframeUrl: url
8383
},
84-
encoding: 'text'
84+
encoding: 'text',
85+
uiMetadata: {
86+
'preferred-frame-size': ['1000px', '700px']
87+
}
8588
});
8689
content.push(uiResource);
8790
}

src/tools/style-comparison-tool/StyleComparisonTool.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,10 @@ export class StyleComparisonTool extends BaseTool<
117117
type: 'externalUrl',
118118
iframeUrl: url
119119
},
120-
encoding: 'text'
120+
encoding: 'text',
121+
uiMetadata: {
122+
'preferred-frame-size': ['1000px', '700px']
123+
}
121124
});
122125
content.push(uiResource);
123126
}

test/config/toolConfig.test.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,23 @@ vi.mock('../../src/utils/versionUtils.js', () => ({
1919
describe('Tool Configuration', () => {
2020
// Save original argv
2121
const originalArgv = process.argv;
22+
const originalEnableMcpUi = process.env.ENABLE_MCP_UI;
2223

2324
beforeEach(() => {
2425
// Reset argv before each test
2526
process.argv = [...originalArgv];
27+
// Reset environment variable before each test
28+
delete process.env.ENABLE_MCP_UI;
2629
});
2730

2831
afterAll(() => {
29-
// Restore original argv
32+
// Restore original argv and env
3033
process.argv = originalArgv;
34+
if (originalEnableMcpUi !== undefined) {
35+
process.env.ENABLE_MCP_UI = originalEnableMcpUi;
36+
} else {
37+
delete process.env.ENABLE_MCP_UI;
38+
}
3139
});
3240

3341
describe('parseToolConfigFromArgs', () => {

test/tools/geojson-preview-tool/GeojsonPreviewTool.test.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,16 +49,22 @@ describe('GeojsonPreviewTool', () => {
4949
}
5050

5151
// Verify MCP-UI resource is included by default
52+
// Could be either Mapbox Static Images API or geojson.io (fallback)
5253
expect(result.content[1]).toMatchObject({
5354
type: 'resource',
5455
resource: {
5556
uri: expect.stringMatching(/^ui:\/\/mapbox\/geojson-preview\//),
5657
mimeType: 'text/uri-list',
57-
text: expect.stringContaining(
58-
'https://geojson.io/#data=data:application/json,'
59-
)
58+
text: expect.stringMatching(/^https:\/\//)
6059
}
6160
});
61+
62+
// Verify the iframe URL is either Mapbox Static Images API or geojson.io
63+
const iframeUrl = (result.content[1] as any).resource.text;
64+
expect(
65+
iframeUrl.includes('api.mapbox.com/styles') ||
66+
iframeUrl.includes('geojson.io')
67+
).toBe(true);
6268
});
6369

6470
it('returns only URL when MCP-UI is disabled', async () => {

0 commit comments

Comments
 (0)