Skip to content

Commit a9d0978

Browse files
committed
skip disabled UI generation
1 parent d07ad87 commit a9d0978

9 files changed

Lines changed: 111 additions & 51 deletions

File tree

src/tests/mcp-ui-utils.test.ts

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
import { describe, expect, test } from '@jest/globals';
1+
import { afterEach, describe, expect, jest, test } from '@jest/globals';
22

33
import {
4+
addUIResourceToResponse,
45
createContextSwitcherUI,
56
createLocatorGeneratorUI,
67
createPageSourceInspectorUI,
78
createTestCodeViewerUI,
9+
createUIResource,
810
} from '../ui/mcp-ui-utils.js';
911

1012
describe('createLocatorGeneratorUI', () => {
@@ -43,6 +45,56 @@ describe('createLocatorGeneratorUI', () => {
4345
});
4446
});
4547

48+
describe('addUIResourceToResponse', () => {
49+
const originalNoUI = process.env.NO_UI;
50+
51+
afterEach(() => {
52+
if (originalNoUI === undefined) {
53+
delete process.env.NO_UI;
54+
} else {
55+
process.env.NO_UI = originalNoUI;
56+
}
57+
});
58+
59+
test.each(['true', '1'])(
60+
'does not construct UI resources when NO_UI=%s',
61+
(value) => {
62+
process.env.NO_UI = value;
63+
const response = { content: [{ type: 'text', text: 'result' }] };
64+
const createResource = jest.fn(() =>
65+
createUIResource('ui://test/resource', '<html></html>')
66+
);
67+
68+
const result = addUIResourceToResponse(response, createResource);
69+
70+
expect(result).toBe(response);
71+
expect(createResource).not.toHaveBeenCalled();
72+
}
73+
);
74+
75+
test('constructs and appends lazy UI resources when UI is enabled', () => {
76+
delete process.env.NO_UI;
77+
const response = { content: [{ type: 'text', text: 'result' }] };
78+
const uiResource = createUIResource('ui://test/resource', '<html></html>');
79+
const createResource = jest.fn(() => uiResource);
80+
81+
const result = addUIResourceToResponse(response, createResource);
82+
83+
expect(createResource).toHaveBeenCalledTimes(1);
84+
expect(result.content).toEqual([...response.content, uiResource]);
85+
});
86+
87+
test('continues to accept prebuilt UI resources', () => {
88+
delete process.env.NO_UI;
89+
const response = { content: [{ type: 'text', text: 'result' }] };
90+
const uiResource = createUIResource('ui://test/resource', '<html></html>');
91+
92+
const result = addUIResourceToResponse(response, uiResource);
93+
94+
expect(result.content).toEqual([...response.content, uiResource]);
95+
});
96+
});
97+
4698
describe('createContextSwitcherUI', () => {
4799
test('escapes context names and avoids inline context handlers', () => {
48100
const html = createContextSwitcherUI(

src/tools/app-management/list-apps.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,12 @@ export async function list(
104104
const textResponse = textResult(
105105
`Installed apps: ${JSON.stringify(apps, null, 2)}`
106106
);
107-
const uiResource = createUIResource(
108-
`ui://appium-mcp/app-list/${Date.now()}`,
109-
createAppListUI(apps)
107+
return addUIResourceToResponse(textResponse, () =>
108+
createUIResource(
109+
`ui://appium-mcp/app-list/${Date.now()}`,
110+
createAppListUI(apps)
111+
)
110112
);
111-
return addUIResourceToResponse(textResponse, uiResource);
112113
} catch (err: unknown) {
113114
return errorResult(`Failed to list apps. err: ${toolErrorMessage(err)}`);
114115
}

src/tools/context/context.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,12 @@ export default function context(server: FastMCP): void {
6969
`Available contexts: ${JSON.stringify(availableContexts, null, 2)}\nCurrent context: ${currentContext}`
7070
);
7171

72-
const uiResource = createUIResource(
73-
`ui://appium-mcp/context-switcher/${Date.now()}`,
74-
createContextSwitcherUI(availableContexts, currentContext)
72+
return addUIResourceToResponse(textResponse, () =>
73+
createUIResource(
74+
`ui://appium-mcp/context-switcher/${Date.now()}`,
75+
createContextSwitcherUI(availableContexts, currentContext)
76+
)
7577
);
76-
77-
return addUIResourceToResponse(textResponse, uiResource);
7878
}
7979

8080
if (!args.context) {

src/tools/interactions/get-page-source.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,12 @@ export default function getPageSource(server: FastMCP): void {
5252
);
5353

5454
// Add interactive page source inspector UI
55-
const uiResource = createUIResource(
56-
`ui://appium-mcp/page-source-inspector/${Date.now()}`,
57-
createPageSourceInspectorUI(pageSource)
55+
return addUIResourceToResponse(textResponse, () =>
56+
createUIResource(
57+
`ui://appium-mcp/page-source-inspector/${Date.now()}`,
58+
createPageSourceInspectorUI(pageSource)
59+
)
5860
);
59-
60-
return addUIResourceToResponse(textResponse, uiResource);
6161
} catch (err: unknown) {
6262
return errorResult(
6363
`Failed to get page source. Error: ${toolErrorMessage(err)}`

src/tools/interactions/screenshot.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -111,12 +111,12 @@ export async function executeScreenshot(opts: {
111111
);
112112

113113
// Add interactive screenshot viewer UI
114-
const uiResource = createUIResource(
115-
`ui://appium-mcp/screenshot-viewer/${Date.now()}`,
116-
createScreenshotViewerUI(displayBase64, filepath)
114+
return addUIResourceToResponse(textResponse, () =>
115+
createUIResource(
116+
`ui://appium-mcp/screenshot-viewer/${Date.now()}`,
117+
createScreenshotViewerUI(displayBase64, filepath)
118+
)
117119
);
118-
119-
return addUIResourceToResponse(textResponse, uiResource);
120120
} catch (err: unknown) {
121121
return errorResult(
122122
`Failed to take screenshot. err: ${toolErrorMessage(err)}`

src/tools/session/create-session.ts

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -455,20 +455,21 @@ export async function createSessionAction(args: {
455455
const textResponse = textResult(
456456
`${platform.toUpperCase()} session created successfully with ID: ${sessionIdStr}\nPlatform: ${finalCapabilities.platformName}\nAutomation: ${finalCapabilities['appium:automationName']}\nDevice: ${finalCapabilities['appium:deviceName']}\nActive sessions: ${totalSessions}`
457457
);
458-
459-
const uiResource = createUIResource(
460-
`ui://appium-mcp/session-dashboard/${sessionIdStr}`,
461-
createSessionDashboardUI({
462-
sessionId: sessionIdStr,
463-
platform: finalCapabilities.platformName,
464-
automationName: finalCapabilities['appium:automationName'],
465-
deviceName: finalCapabilities['appium:deviceName'],
466-
platformVersion: finalCapabilities['appium:platformVersion'],
467-
udid: finalCapabilities['appium:udid'],
468-
})
458+
const sessionCapabilities = finalCapabilities;
459+
460+
return addUIResourceToResponse(textResponse, () =>
461+
createUIResource(
462+
`ui://appium-mcp/session-dashboard/${sessionIdStr}`,
463+
createSessionDashboardUI({
464+
sessionId: sessionIdStr,
465+
platform: sessionCapabilities.platformName,
466+
automationName: sessionCapabilities['appium:automationName'],
467+
deviceName: sessionCapabilities['appium:deviceName'],
468+
platformVersion: sessionCapabilities['appium:platformVersion'],
469+
udid: sessionCapabilities['appium:udid'],
470+
})
471+
)
469472
);
470-
471-
return addUIResourceToResponse(textResponse, uiResource);
472473
} catch (error: unknown) {
473474
log.error('Error creating session:', error);
474475
return errorResult(

src/tools/session/select-device.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -225,12 +225,12 @@ function formatAndroidListResponse(devices: any[]): ContentResult {
225225
);
226226

227227
// Add interactive UI picker
228-
const uiResource = createUIResource(
229-
`ui://appium-mcp/device-picker/android-${Date.now()}`,
230-
createDevicePickerUI(devices, 'android')
228+
return addUIResourceToResponse(textResponse, () =>
229+
createUIResource(
230+
`ui://appium-mcp/device-picker/android-${Date.now()}`,
231+
createDevicePickerUI(devices, 'android')
232+
)
231233
);
232-
233-
return addUIResourceToResponse(textResponse, uiResource);
234234
}
235235

236236
/**
@@ -344,12 +344,12 @@ function formatIOSListResponse(
344344
);
345345

346346
// Add interactive UI picker
347-
const uiResource = createUIResource(
348-
`ui://appium-mcp/device-picker/ios-${iosDeviceType}-${Date.now()}`,
349-
createDevicePickerUI(devices, 'ios', iosDeviceType)
347+
return addUIResourceToResponse(textResponse, () =>
348+
createUIResource(
349+
`ui://appium-mcp/device-picker/ios-${iosDeviceType}-${Date.now()}`,
350+
createDevicePickerUI(devices, 'ios', iosDeviceType)
351+
)
350352
);
351-
352-
return addUIResourceToResponse(textResponse, uiResource);
353353
}
354354

355355
/**

src/tools/test-generation/locators.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,12 +85,12 @@ export default function generateLocators(server: any): void {
8585
})
8686
);
8787

88-
const uiResource = createUIResource(
89-
`ui://appium-mcp/locator-generator/${Date.now()}`,
90-
createLocatorGeneratorUI(interactableElements)
88+
return addUIResourceToResponse(textResponse, () =>
89+
createUIResource(
90+
`ui://appium-mcp/locator-generator/${Date.now()}`,
91+
createLocatorGeneratorUI(interactableElements)
92+
)
9193
);
92-
93-
return addUIResourceToResponse(textResponse, uiResource);
9494
} catch (err: unknown) {
9595
log.error('Error getting page source:', err);
9696
return errorResult(

src/ui/mcp-ui-utils.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1678,18 +1678,24 @@ export function createTestCodeViewerUI(
16781678

16791679
/**
16801680
* Helper function to add UI resource to response content
1681-
* Returns both text and UI resource for backward compatibility
1681+
* Accepts a lazy factory so NO_UI can skip expensive UI construction.
1682+
* Returns both text and UI resource for backward compatibility.
16821683
*/
16831684
export function addUIResourceToResponse(
16841685
response: { content: Array<{ type: string; text?: string }> },
1685-
uiResource: ReturnType<typeof createUIResource>
1686+
uiResource:
1687+
| ReturnType<typeof createUIResource>
1688+
| (() => ReturnType<typeof createUIResource>)
16861689
): { content: Array<any> } {
16871690
if (process.env.NO_UI === 'true' || process.env.NO_UI === '1') {
16881691
return response;
16891692
}
16901693

1694+
const resolvedUIResource =
1695+
typeof uiResource === 'function' ? uiResource() : uiResource;
1696+
16911697
return {
1692-
content: [...response.content, uiResource],
1698+
content: [...response.content, resolvedUIResource],
16931699
};
16941700
}
16951701

0 commit comments

Comments
 (0)