Skip to content

Commit 634eff2

Browse files
Add descriptive text content to MCP-UI tools for better client compatibility
Similar to mcp-server PR #104, enhance text content in tool responses to follow progressive enhancement pattern for better MCP spec compliance. Changes: - preview_style_tool: Add descriptive metadata (style ID, options, URL) - style_comparison_tool: Add comparison metadata (before/after styles, view position, URL) - geojson_preview_tool: Add GeoJSON metadata (type, feature count, geometry types, URL) - Update tests to verify new descriptive text format This ensures all MCP clients (including text-only clients) can display meaningful information, not just URLs. The text content includes: - Success confirmation - Key metadata about the operation - The preview/comparison URL Text content appears as first element (progressive enhancement): 1. Text description (for all clients) 2. MCP-UI resource (for MCP-UI clients) 3. MCP Apps metadata (for MCP Apps clients) Related: mcp-server#104 All tests passing (520 tests).
1 parent ea5cf83 commit 634eff2

4 files changed

Lines changed: 98 additions & 14 deletions

File tree

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

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,11 +105,39 @@ export class GeojsonPreviewTool extends BaseTool<typeof GeojsonPreviewSchema> {
105105
const encodedGeoJSON = encodeURIComponent(geojsonString);
106106
const geojsonIOUrl = `https://geojson.io/#data=data:application/json,${encodedGeoJSON}`;
107107

108-
// Build content array with URL
108+
// Extract GeoJSON metadata for descriptive text
109+
const geojsonType = (geojsonData as { type: string }).type;
110+
let featureInfo = '';
111+
if (geojsonType === 'FeatureCollection') {
112+
const fc = geojsonData as {
113+
features: Array<{ geometry: { type: string } }>;
114+
};
115+
const featureCount = fc.features.length;
116+
const geometryTypes = [
117+
...new Set(fc.features.map((f) => f.geometry.type))
118+
];
119+
featureInfo = `Features: ${featureCount} (${geometryTypes.join(', ')})`;
120+
} else if (geojsonType === 'Feature') {
121+
const feature = geojsonData as { geometry: { type: string } };
122+
featureInfo = `Geometry: ${feature.geometry.type}`;
123+
} else {
124+
featureInfo = `Geometry: ${geojsonType}`;
125+
}
126+
127+
// Build descriptive text with GeoJSON metadata for better client compatibility
128+
// This ensures all MCP clients can display meaningful information
129+
const textDescription = [
130+
'GeoJSON preview generated successfully.',
131+
`Type: ${geojsonType}`,
132+
featureInfo,
133+
`Preview URL: ${geojsonIOUrl}`
134+
].join('\n');
135+
136+
// Build content array with text first (for compatibility)
109137
const content: CallToolResult['content'] = [
110138
{
111139
type: 'text',
112-
text: geojsonIOUrl
140+
text: textDescription
113141
}
114142
];
115143

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

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,25 @@ export class PreviewStyleTool extends BaseTool<typeof PreviewStyleSchema> {
6565

6666
const url = `${MapboxApiBasedTool.mapboxApiEndpoint}styles/v1/${userName}/${input.styleId}.html?${params.toString()}${hashFragment}`;
6767

68-
// Build content array with URL
68+
// Build descriptive text with map metadata for better client compatibility
69+
// This ensures all MCP clients can display meaningful information
70+
const textDescription = [
71+
'Mapbox style preview generated successfully.',
72+
`Style: ${userName}/${input.styleId}`,
73+
`Preview URL: ${url}`,
74+
input.title !== undefined ? `Title display: ${input.title}` : null,
75+
input.zoomwheel !== undefined
76+
? `Zoom control: ${input.zoomwheel ? 'enabled' : 'disabled'}`
77+
: null
78+
]
79+
.filter(Boolean)
80+
.join('\n');
81+
82+
// Build content array with text first (for compatibility)
6983
const content: CallToolResult['content'] = [
7084
{
7185
type: 'text',
72-
text: url
86+
text: textDescription
7387
}
7488
];
7589

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

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,11 +101,27 @@ export class StyleComparisonTool extends BaseTool<
101101
url += `#${input.zoom}/${input.latitude}/${input.longitude}`;
102102
}
103103

104-
// Build content array with URL
104+
// Build descriptive text with comparison metadata for better client compatibility
105+
// This ensures all MCP clients can display meaningful information
106+
const textDescription = [
107+
'Mapbox style comparison generated successfully.',
108+
`Before: ${beforeStyleId}`,
109+
`After: ${afterStyleId}`,
110+
input.zoom !== undefined &&
111+
input.latitude !== undefined &&
112+
input.longitude !== undefined
113+
? `View: ${input.latitude}, ${input.longitude} @ zoom ${input.zoom}`
114+
: null,
115+
`Comparison URL: ${url}`
116+
]
117+
.filter(Boolean)
118+
.join('\n');
119+
120+
// Build content array with text first (for compatibility)
105121
const content: CallToolResult['content'] = [
106122
{
107123
type: 'text',
108-
text: url
124+
text: textDescription
109125
}
110126
];
111127

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

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,14 @@ describe('GeojsonPreviewTool', () => {
4040
expect(result.content[0].type).toBe('text');
4141
const content = result.content[0];
4242
if (content.type === 'text') {
43-
expect(content.text).toMatch(
44-
/^https:\/\/geojson\.io\/#data=data:application\/json,/
43+
// Verify descriptive text includes metadata
44+
expect(content.text).toContain('GeoJSON preview generated successfully');
45+
expect(content.text).toContain('Type: Point');
46+
expect(content.text).toContain('Geometry: Point');
47+
expect(content.text).toContain('Preview URL:');
48+
// Verify URL is present in the text
49+
expect(content.text).toContain(
50+
'https://geojson.io/#data=data:application/json,'
4551
);
4652
expect(content.text).toContain(
4753
encodeURIComponent(JSON.stringify(pointGeoJSON))
@@ -104,8 +110,14 @@ describe('GeojsonPreviewTool', () => {
104110
expect(result.isError).toBe(false);
105111
const content = result.content[0];
106112
if (content.type === 'text') {
107-
expect(content.text).toMatch(
108-
/^https:\/\/geojson\.io\/#data=data:application\/json,/
113+
// Verify descriptive text includes metadata
114+
expect(content.text).toContain('GeoJSON preview generated successfully');
115+
expect(content.text).toContain('Type: Feature');
116+
expect(content.text).toContain('Geometry: Point');
117+
expect(content.text).toContain('Preview URL:');
118+
// Verify URL is present in the text
119+
expect(content.text).toContain(
120+
'https://geojson.io/#data=data:application/json,'
109121
);
110122
expect(content.text).toContain(encodeURIComponent(geoJSONString));
111123
}
@@ -142,8 +154,16 @@ describe('GeojsonPreviewTool', () => {
142154
expect(result.isError).toBe(false);
143155
const content = result.content[0];
144156
if (content.type === 'text') {
145-
expect(content.text).toMatch(
146-
/^https:\/\/geojson\.io\/#data=data:application\/json,/
157+
// Verify descriptive text includes metadata
158+
expect(content.text).toContain('GeoJSON preview generated successfully');
159+
expect(content.text).toContain('Type: FeatureCollection');
160+
expect(content.text).toContain('Features: 2');
161+
expect(content.text).toContain('Point');
162+
expect(content.text).toContain('LineString');
163+
expect(content.text).toContain('Preview URL:');
164+
// Verify URL is present in the text
165+
expect(content.text).toContain(
166+
'https://geojson.io/#data=data:application/json,'
147167
);
148168
expect(content.text).toContain(
149169
encodeURIComponent(JSON.stringify(featureCollection))
@@ -197,8 +217,14 @@ describe('GeojsonPreviewTool', () => {
197217
expect(result.isError).toBe(false);
198218
const content = result.content[0];
199219
if (content.type === 'text') {
200-
expect(content.text).toMatch(
201-
/^https:\/\/geojson\.io\/#data=data:application\/json,/
220+
// Verify descriptive text includes metadata
221+
expect(content.text).toContain('GeoJSON preview generated successfully');
222+
expect(content.text).toContain('Type: Feature');
223+
expect(content.text).toContain('Geometry: Point');
224+
expect(content.text).toContain('Preview URL:');
225+
// Verify URL is present in the text
226+
expect(content.text).toContain(
227+
'https://geojson.io/#data=data:application/json,'
202228
);
203229
// Verify URL contains properly encoded content
204230
expect(content.text).toContain('%22'); // Encoded quotes

0 commit comments

Comments
 (0)