appium-mcp 1.88.5 → 1.90.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +27 -0
  3. package/dist/resources/index.d.ts.map +1 -1
  4. package/dist/resources/index.js +5 -0
  5. package/dist/resources/index.js.map +1 -1
  6. package/dist/resources/page-source-inspector.d.ts +4 -0
  7. package/dist/resources/page-source-inspector.d.ts.map +1 -0
  8. package/dist/resources/page-source-inspector.js +15 -0
  9. package/dist/resources/page-source-inspector.js.map +1 -0
  10. package/dist/telemetry/wrapOperations.d.ts +8 -0
  11. package/dist/telemetry/wrapOperations.d.ts.map +1 -1
  12. package/dist/telemetry/wrapOperations.js +97 -2
  13. package/dist/telemetry/wrapOperations.js.map +1 -1
  14. package/dist/tools/interactions/get-page-source.d.ts.map +1 -1
  15. package/dist/tools/interactions/get-page-source.js +11 -2
  16. package/dist/tools/interactions/get-page-source.js.map +1 -1
  17. package/dist/ui/mcp-apps.d.ts +15 -0
  18. package/dist/ui/mcp-apps.d.ts.map +1 -0
  19. package/dist/ui/mcp-apps.js +44 -0
  20. package/dist/ui/mcp-apps.js.map +1 -0
  21. package/dist/ui/page-source-inspector-app.d.ts +8 -0
  22. package/dist/ui/page-source-inspector-app.d.ts.map +1 -0
  23. package/dist/ui/page-source-inspector-app.js +250 -0
  24. package/dist/ui/page-source-inspector-app.js.map +1 -0
  25. package/package.json +1 -1
  26. package/server.json +2 -2
  27. package/src/resources/index.ts +5 -0
  28. package/src/resources/page-source-inspector.ts +18 -0
  29. package/src/telemetry/wrapOperations.ts +109 -2
  30. package/src/tools/interactions/get-page-source.ts +12 -5
  31. package/src/ui/mcp-apps.ts +59 -0
  32. package/src/ui/page-source-inspector-app.ts +249 -0
package/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## [1.90.0](https://github.com/appium/appium-mcp/compare/v1.89.0...v1.90.0) (2026-07-28)
2
+
3
+ ### Features
4
+
5
+ * avoid duplicate page source UI payloads ([#477](https://github.com/appium/appium-mcp/issues/477)) ([b8370df](https://github.com/appium/appium-mcp/commit/b8370df42e6f6f46e043ba31b0935bede44465a3))
6
+
7
+ ## [1.89.0](https://github.com/appium/appium-mcp/compare/v1.88.5...v1.89.0) (2026-07-26)
8
+
9
+ ### Features
10
+
11
+ * add payload-free tool result size telemetry ([#476](https://github.com/appium/appium-mcp/issues/476)) ([ca0262d](https://github.com/appium/appium-mcp/commit/ca0262d38a18747f08c1f08c94099c7b82150272))
12
+
1
13
  ## [1.88.5](https://github.com/appium/appium-mcp/compare/v1.88.4...v1.88.5) (2026-07-26)
2
14
 
3
15
  ### Performance Improvements
package/README.md CHANGED
@@ -149,6 +149,7 @@ This will automatically configure the MCP server for use with Claude Code. Make
149
149
  | `CAPABILITIES_CONFIG` | Optional | Absolute path to a `capabilities.json` file with per-platform capability presets |
150
150
  | `SCREENSHOTS_DIR` | Optional | Directory where screenshots and screen recordings are saved. Defaults to the current working directory |
151
151
  | `NO_UI` | Optional | Set to `true` or `1` to disable HTML UI components — faster responses, fewer tokens. See [NO_UI Mode](#no_ui-mode) |
152
+ | `APPIUM_MCP_APPS_ENABLED` | Optional | MCP Apps static UI mode. Enabled by default. Set to `false` or `0` to force the embedded UI compatibility fallback. See [MCP Apps Mode](#mcp-apps-mode) |
152
153
  | `APPIUM_MCP_ON_CLIENT_DISCONNECT` | Optional | Session cleanup when the MCP client disconnects: `delete_all` (default) deletes **MCP-owned** Appium sessions (`safeDeleteAllSessions`); `skip` keeps those sessions across disconnects (e.g. HTTP/stream clients that reconnect). Attached/remote sessions are not removed by this path. See [MCP disconnect behavior](#mcp-disconnect-behavior). |
153
154
  | `APPIUM_MCP_WDA_APP_PATH` | Optional | Absolute path to a pre-extracted `WebDriverAgentRunner-Runner.app` bundle. When set, `prepare_ios_simulator` skips all GitHub downloads and uses this bundle directly — useful in environments where external downloads are blocked |
154
155
  | `REMOTE_SERVER_URL_ALLOW_REGEX` | Optional | Regex pattern that remote Appium server URLs must match. Defaults to `^https?://` |
@@ -189,6 +190,8 @@ OTEL_TRACES_SAMPLER=parentbased_always_on
189
190
 
190
191
  When enabled, appium-mcp creates spans for MCP tool calls, prompt loads, resource reads, and resource template reads. Error status is recorded for thrown operation errors and MCP tool results marked with `isError`. Span attributes intentionally avoid raw screenshots, XML page source, prompts, credentials, and other high-cardinality or sensitive payloads.
191
192
 
193
+ Tool spans include payload-free result-size attributes: `mcp.tool.result.content_count`, `content_types`, `text_chars`, `resource_count`, `resource_text_chars`, `image_count`, `audio_count`, `base64_chars`, and `base64_bytes_estimate` (all prefixed with `mcp.tool.result.`). `content_types` contains only known MCP types or `other`; payload values, resource URIs, MIME types, and unknown type strings are never recorded. Sizes are counted directly from known result fields without serializing or copying the complete result.
194
+
192
195
  For local trace inspection, use the Jaeger setup in `tools/telemetry`:
193
196
 
194
197
  ```bash
@@ -326,6 +329,30 @@ More models benchmarked can be found [here](src/tests/benchmark_model/TEST_REPOR
326
329
 
327
330
  ### Performance Optimization
328
331
 
332
+ #### MCP Apps Mode
333
+
334
+ `appium_get_page_source` uses a static MCP App inspector by default when the client advertises MCP Apps support.
335
+ The XML remains in the normal text result for the LLM, while the inspector reads that same result instead of
336
+ receiving a duplicated XML copy.
337
+
338
+ For clients with unreliable MCP Apps rendering, set `APPIUM_MCP_APPS_ENABLED` to `false` or `0`:
339
+
340
+ ```json
341
+ {
342
+ "appium-mcp": {
343
+ "env": {
344
+ "APPIUM_MCP_APPS_ENABLED": "false"
345
+ }
346
+ }
347
+ }
348
+ ```
349
+
350
+ This keeps interactive UI enabled but forces the previous embedded page-source inspector. The compatibility mode
351
+ duplicates the XML inside the inspector HTML and therefore uses more result tokens and bandwidth. With a synthetic
352
+ 95,000-character page source, the static mode reduced the result from approximately 267 KB to 95 KB (about 64%).
353
+
354
+ `NO_UI=true` or `NO_UI=1` takes precedence over this setting and disables both static and embedded UI.
355
+
329
356
  #### NO_UI Mode
330
357
 
331
358
  Set the `NO_UI` environment variable to `true` or `1` to disable UI components and improve performance:
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/resources/index.ts"],"names":[],"mappings":"AAIA,MAAM,CAAC,OAAO,UAAU,iBAAiB,CAAC,MAAM,EAAE,GAAG,QAGpD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/resources/index.ts"],"names":[],"mappings":"AAMA,MAAM,CAAC,OAAO,UAAU,iBAAiB,CAAC,MAAM,EAAE,GAAG,QAMpD"}
@@ -1,8 +1,13 @@
1
1
  import log from '../logger.js';
2
+ import { isMcpAppsEnabled } from '../ui/mcp-apps.js';
2
3
  // Export all resources
3
4
  import javaTemplatesResource from './java/template.js';
5
+ import pageSourceInspectorResource from './page-source-inspector.js';
4
6
  export default function registerResources(server) {
5
7
  javaTemplatesResource(server);
8
+ if (isMcpAppsEnabled()) {
9
+ pageSourceInspectorResource(server);
10
+ }
6
11
  log.info('All resources registered');
7
12
  }
8
13
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/resources/index.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,cAAc,CAAC;AAC/B,uBAAuB;AACvB,OAAO,qBAAqB,MAAM,oBAAoB,CAAC;AAEvD,MAAM,CAAC,OAAO,UAAU,iBAAiB,CAAC,MAAW;IACnD,qBAAqB,CAAC,MAAM,CAAC,CAAC;IAC9B,GAAG,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;AACvC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/resources/index.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,cAAc,CAAC;AAC/B,OAAO,EAAC,gBAAgB,EAAC,MAAM,mBAAmB,CAAC;AACnD,uBAAuB;AACvB,OAAO,qBAAqB,MAAM,oBAAoB,CAAC;AACvD,OAAO,2BAA2B,MAAM,4BAA4B,CAAC;AAErE,MAAM,CAAC,OAAO,UAAU,iBAAiB,CAAC,MAAW;IACnD,qBAAqB,CAAC,MAAM,CAAC,CAAC;IAC9B,IAAI,gBAAgB,EAAE,EAAE,CAAC;QACvB,2BAA2B,CAAC,MAAM,CAAC,CAAC;IACtC,CAAC;IACD,GAAG,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;AACvC,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { FastMCP } from 'fastmcp';
2
+ export declare const PAGE_SOURCE_INSPECTOR_URI = "ui://appium-mcp/page-source-inspector";
3
+ export default function pageSourceInspectorResource(server: FastMCP): void;
4
+ //# sourceMappingURL=page-source-inspector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-source-inspector.d.ts","sourceRoot":"","sources":["../../src/resources/page-source-inspector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,SAAS,CAAC;AAKrC,eAAO,MAAM,yBAAyB,0CAA0C,CAAC;AAEjF,MAAM,CAAC,OAAO,UAAU,2BAA2B,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAUzE"}
@@ -0,0 +1,15 @@
1
+ import { MCP_APP_MIME_TYPE } from '../ui/mcp-apps.js';
2
+ import { createPageSourceInspectorAppUI } from '../ui/page-source-inspector-app.js';
3
+ export const PAGE_SOURCE_INSPECTOR_URI = 'ui://appium-mcp/page-source-inspector';
4
+ export default function pageSourceInspectorResource(server) {
5
+ server.addResource({
6
+ uri: PAGE_SOURCE_INSPECTOR_URI,
7
+ name: 'Appium Page Source Inspector',
8
+ description: 'Interactive inspector for appium_get_page_source results',
9
+ mimeType: MCP_APP_MIME_TYPE,
10
+ async load() {
11
+ return { text: createPageSourceInspectorAppUI() };
12
+ },
13
+ });
14
+ }
15
+ //# sourceMappingURL=page-source-inspector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-source-inspector.js","sourceRoot":"","sources":["../../src/resources/page-source-inspector.ts"],"names":[],"mappings":"AAEA,OAAO,EAAC,iBAAiB,EAAC,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAC,8BAA8B,EAAC,MAAM,oCAAoC,CAAC;AAElF,MAAM,CAAC,MAAM,yBAAyB,GAAG,uCAAuC,CAAC;AAEjF,MAAM,CAAC,OAAO,UAAU,2BAA2B,CAAC,MAAe;IACjE,MAAM,CAAC,WAAW,CAAC;QACjB,GAAG,EAAE,yBAAyB;QAC9B,IAAI,EAAE,8BAA8B;QACpC,WAAW,EAAE,0DAA0D;QACvE,QAAQ,EAAE,iBAAiB;QAC3B,KAAK,CAAC,IAAI;YACR,OAAO,EAAC,IAAI,EAAE,8BAA8B,EAAE,EAAC,CAAC;QAClD,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
@@ -25,6 +25,14 @@ export declare function installTelemetryWrappers(server: FastMCP): void;
25
25
  * @returns A new tool definition with telemetry spans around the execute function.
26
26
  */
27
27
  export declare function wrapToolWithTelemetry(toolDef: ToolDef): ToolDef;
28
+ /**
29
+ * Builds payload-free size attributes for a tool result.
30
+ *
31
+ * This intentionally counts known MCP content fields directly instead of
32
+ * serializing the whole result. Large page sources, UI HTML, and base64 data
33
+ * therefore are not copied or recorded in telemetry.
34
+ */
35
+ export declare function safeToolResultSizeAttributes(result: unknown): Record<string, number | string[]>;
28
36
  export declare function safeInputValueAttributes(args: unknown): Record<string, string | number | boolean | string[]>;
29
37
  export {};
30
38
  //# sourceMappingURL=wrapOperations.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"wrapOperations.d.ts","sourceRoot":"","sources":["../../src/telemetry/wrapOperations.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,SAAS,CAAC;AAMrC,KAAK,OAAO,GAAG,UAAU,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAUjD;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAwB9D;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAoB/D;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,EAAE,CAAC,CAmB5G"}
1
+ {"version":3,"file":"wrapOperations.d.ts","sourceRoot":"","sources":["../../src/telemetry/wrapOperations.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,SAAS,CAAC;AAMrC,KAAK,OAAO,GAAG,UAAU,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAYjD;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAwB9D;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAsB/D;AAED;;;;;;GAMG;AACH,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAwE/F;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,EAAE,CAAC,CAmB5G"}
@@ -7,6 +7,7 @@
7
7
  import { isSensitiveKey } from '../utils/sensitive.js';
8
8
  import { isArgumentValueTelemetryEnabled, safeAttributeValue, safeInputKeys, safeSessionId } from './attributes.js';
9
9
  import { getActiveSpan, SpanStatusCode, withSpan } from './tracer.js';
10
+ const SAFE_TOOL_CONTENT_TYPES = new Set(['audio', 'image', 'resource', 'resource_link', 'text']);
10
11
  /**
11
12
  * Installs telemetry wrappers on the given FastMCP server instance. This should be
12
13
  * called early in the server setup process, before registering any tools, prompts,
@@ -50,14 +51,88 @@ export function wrapToolWithTelemetry(toolDef) {
50
51
  ...toolDef,
51
52
  execute: async (args, context) => withSpan(`tools/call ${toolName}`, toolAttributes(toolName, args), async () => {
52
53
  const result = await execute(args, context);
54
+ const span = getActiveSpan();
55
+ span?.setAttributes(safeToolResultSizeAttributes(result));
53
56
  if (isErrorResult(result)) {
54
- getActiveSpan()?.setStatus({ code: SpanStatusCode.ERROR });
55
- getActiveSpan()?.setAttribute('mcp.tool.result.is_error', true);
57
+ span?.setStatus({ code: SpanStatusCode.ERROR });
58
+ span?.setAttribute('mcp.tool.result.is_error', true);
56
59
  }
57
60
  return result;
58
61
  }),
59
62
  };
60
63
  }
64
+ /**
65
+ * Builds payload-free size attributes for a tool result.
66
+ *
67
+ * This intentionally counts known MCP content fields directly instead of
68
+ * serializing the whole result. Large page sources, UI HTML, and base64 data
69
+ * therefore are not copied or recorded in telemetry.
70
+ */
71
+ export function safeToolResultSizeAttributes(result) {
72
+ if (typeof result === 'string') {
73
+ return { 'mcp.tool.result.text_chars': result.length };
74
+ }
75
+ if (!isRecord(result) || !Array.isArray(result.content)) {
76
+ return {};
77
+ }
78
+ const contentTypes = new Set();
79
+ let textChars = 0;
80
+ let resourceCount = 0;
81
+ let resourceTextChars = 0;
82
+ let imageCount = 0;
83
+ let audioCount = 0;
84
+ let base64Chars = 0;
85
+ let base64BytesEstimate = 0;
86
+ for (const content of result.content) {
87
+ if (!isRecord(content)) {
88
+ contentTypes.add('other');
89
+ continue;
90
+ }
91
+ const contentType = typeof content.type === 'string' && SAFE_TOOL_CONTENT_TYPES.has(content.type) ? content.type : 'other';
92
+ contentTypes.add(contentType);
93
+ if (contentType === 'text' && typeof content.text === 'string') {
94
+ textChars += content.text.length;
95
+ continue;
96
+ }
97
+ if (contentType === 'image' || contentType === 'audio') {
98
+ if (contentType === 'image') {
99
+ imageCount += 1;
100
+ }
101
+ else {
102
+ audioCount += 1;
103
+ }
104
+ if (typeof content.data === 'string') {
105
+ base64Chars += content.data.length;
106
+ base64BytesEstimate += estimateBase64DecodedBytes(content.data);
107
+ }
108
+ continue;
109
+ }
110
+ if (contentType === 'resource') {
111
+ resourceCount += 1;
112
+ if (isRecord(content.resource)) {
113
+ if (typeof content.resource.text === 'string') {
114
+ resourceTextChars += content.resource.text.length;
115
+ }
116
+ if (typeof content.resource.blob === 'string') {
117
+ base64Chars += content.resource.blob.length;
118
+ base64BytesEstimate += estimateBase64DecodedBytes(content.resource.blob);
119
+ }
120
+ }
121
+ }
122
+ }
123
+ const attributes = {
124
+ 'mcp.tool.result.content_count': result.content.length,
125
+ 'mcp.tool.result.content_types': [...contentTypes].sort(),
126
+ 'mcp.tool.result.text_chars': textChars,
127
+ 'mcp.tool.result.resource_count': resourceCount,
128
+ 'mcp.tool.result.resource_text_chars': resourceTextChars,
129
+ 'mcp.tool.result.image_count': imageCount,
130
+ 'mcp.tool.result.audio_count': audioCount,
131
+ 'mcp.tool.result.base64_chars': base64Chars,
132
+ 'mcp.tool.result.base64_bytes_estimate': base64BytesEstimate,
133
+ };
134
+ return attributes;
135
+ }
61
136
  export function safeInputValueAttributes(args) {
62
137
  if (!isArgumentValueTelemetryEnabled()) {
63
138
  return {};
@@ -150,4 +225,24 @@ function inputKeyAttributes(args) {
150
225
  function isErrorResult(result) {
151
226
  return (!!result && typeof result === 'object' && 'isError' in result && result.isError === true);
152
227
  }
228
+ function isRecord(value) {
229
+ return !!value && typeof value === 'object' && !Array.isArray(value);
230
+ }
231
+ function estimateBase64DecodedBytes(value) {
232
+ const commaIndex = value.indexOf(',');
233
+ const hasDataUriPrefix = commaIndex >= 7 && value.slice(commaIndex - 7, commaIndex).toLowerCase() === ';base64';
234
+ const contentStart = hasDataUriPrefix ? commaIndex + 1 : 0;
235
+ const encodedLength = value.length - contentStart;
236
+ if (encodedLength <= 0) {
237
+ return 0;
238
+ }
239
+ let padding = 0;
240
+ if (value.endsWith('==')) {
241
+ padding = 2;
242
+ }
243
+ else if (value.endsWith('=')) {
244
+ padding = 1;
245
+ }
246
+ return Math.max(0, Math.floor((encodedLength * 3) / 4) - padding);
247
+ }
153
248
  //# sourceMappingURL=wrapOperations.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"wrapOperations.js","sourceRoot":"","sources":["../../src/telemetry/wrapOperations.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,EAAC,cAAc,EAAC,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAC,+BAA+B,EAAE,kBAAkB,EAAE,aAAa,EAAE,aAAa,EAAC,MAAM,iBAAiB,CAAC;AAClH,OAAO,EAAC,aAAa,EAAE,cAAc,EAAE,QAAQ,EAAC,MAAM,aAAa,CAAC;AAYpE;;;;;;;;GAQG;AACH,MAAM,UAAU,wBAAwB,CAAC,MAAe;IACtD,MAAM,eAAe,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAuB,CAAC;IAE1E,MAAM,CAAC,OAAO,GAAG,CAAC,CAAC,OAAgB,EAAE,EAAE,CAAC,eAAe,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC,CAAuB,CAAC;IAE/G,IAAI,OAAO,MAAM,CAAC,SAAS,KAAK,UAAU,EAAE,CAAC;QAC3C,MAAM,iBAAiB,GAAG,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAyB,CAAC;QAChF,MAAM,CAAC,SAAS,GAAG,CAAC,CAAC,SAAoB,EAAE,EAAE,CAC3C,iBAAiB,CAAC,uBAAuB,CAAC,SAAS,CAAC,CAAC,CAAyB,CAAC;IACnF,CAAC;IAED,IAAI,OAAO,MAAM,CAAC,WAAW,KAAK,UAAU,EAAE,CAAC;QAC7C,MAAM,mBAAmB,GAAG,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,CAA2B,CAAC;QACtF,MAAM,CAAC,WAAW,GAAG,CAAC,CAAC,WAAwB,EAAE,EAAE,CACjD,mBAAmB,CAAC,yBAAyB,CAAC,WAAW,CAAC,CAAC,CAA2B,CAAC;IAC3F,CAAC;IAED,IAAI,OAAO,MAAM,CAAC,mBAAmB,KAAK,UAAU,EAAE,CAAC;QACrD,MAAM,2BAA2B,GAAG,MAAM,CAAC,mBAAmB,CAAC,IAAI,CAAC,MAAM,CAAmC,CAAC;QAC9G,MAAM,CAAC,mBAAmB,GAAG,CAAC,CAAC,mBAAwC,EAAE,EAAE,CACzE,2BAA2B,CACzB,iCAAiC,CAAC,mBAAmB,CAAC,CACvD,CAAmC,CAAC;IACzC,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,qBAAqB,CAAC,OAAgB;IACpD,MAAM,OAAO,GAAG,OAAO,CAAC,OAAkC,CAAC;IAC3D,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,IAAI,cAAc,CAAC;IAEhD,OAAO;QACL,GAAG,OAAO;QACV,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,CAC/B,QAAQ,CAAC,cAAc,QAAQ,EAAE,EAAE,cAAc,CAAC,QAAQ,EAAE,IAAI,CAAC,EAAE,KAAK,IAAI,EAAE;YAC5E,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YAC5C,IAAI,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC1B,aAAa,EAAE,EAAE,SAAS,CAAC,EAAC,IAAI,EAAE,cAAc,CAAC,KAAK,EAAC,CAAC,CAAC;gBACzD,aAAa,EAAE,EAAE,YAAY,CAAC,0BAA0B,EAAE,IAAI,CAAC,CAAC;YAClE,CAAC;YACD,OAAO,MAAM,CAAC;QAChB,CAAC,CAAC;KACL,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,wBAAwB,CAAC,IAAa;IACpD,IAAI,CAAC,+BAA+B,EAAE,EAAE,CAAC;QACvC,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7D,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,UAAU,GAAyD,EAAE,CAAC;IAE5E,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QAChD,0FAA0F;QAC1F,IAAI,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC;YACxB,SAAS;QACX,CAAC;QACD,UAAU,CAAC,mBAAmB,GAAG,EAAE,CAAC,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC;IACnE,CAAC;IAED,OAAO,UAAU,CAAC;AACpB,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,uBAAuB,CAAC,SAAoB;IACnD,MAAM,IAAI,GAAG,SAAS,CAAC,IAA8B,CAAC;IACtD,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,UAAU,GAAG,SAAS,CAAC,IAAI,IAAI,gBAAgB,CAAC;IAEtD,OAAO;QACL,GAAG,SAAS;QACZ,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,CACzB,QAAQ,CACN,eAAe,UAAU,EAAE,EAC3B;YACE,iBAAiB,EAAE,UAAU;YAC7B,GAAG,eAAe,CAAC,IAAI,CAAC;SACzB,EACD,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CACvB;KACJ,CAAC;AACJ,CAAC;AAED,SAAS,yBAAyB,CAAC,WAAwB;IACzD,MAAM,IAAI,GAAG,WAAW,CAAC,IAAgC,CAAC;IAC1D,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,WAAW,CAAC;IACrB,CAAC;IAED,MAAM,GAAG,GAAG,WAAW,CAAC,GAAG,IAAI,kBAAkB,CAAC;IAElD,OAAO;QACL,GAAG,WAAW;QACd,IAAI,EAAE,KAAK,IAAI,EAAE,CACf,QAAQ,CACN,gBAAgB,EAChB;YACE,kBAAkB,EAAE,GAAG;SACxB,EACD,GAAG,EAAE,CAAC,IAAI,EAAE,CACb;KACJ,CAAC;AACJ,CAAC;AAED,SAAS,iCAAiC,CAAC,mBAAwC;IACjF,MAAM,IAAI,GAAG,mBAAmB,CAAC,IAAwC,CAAC;IAC1E,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,mBAAmB,CAAC;IAC7B,CAAC;IAED,MAAM,WAAW,GAAG,mBAAmB,CAAC,WAAW,EAAE,QAAQ,EAAE,IAAI,2BAA2B,CAAC;IAE/F,OAAO;QACL,GAAG,mBAAmB;QACtB,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,CACzB,QAAQ,CACN,gBAAgB,EAChB;YACE,2BAA2B,EAAE,WAAW;YACxC,GAAG,eAAe,CAAC,IAAI,CAAC;SACzB,EACD,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CACvB;KACJ,CAAC;AACJ,CAAC;AAED,SAAS,cAAc,CAAC,QAAgB,EAAE,IAAa;IACrD,MAAM,UAAU,GAAsC;QACpD,eAAe,EAAE,QAAQ;KAC1B,CAAC;IAEF,MAAM,SAAS,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;IACtC,IAAI,SAAS,EAAE,CAAC;QACd,UAAU,CAAC,mBAAmB,CAAC,GAAG,SAAS,CAAC;IAC9C,CAAC;IAED,OAAO;QACL,GAAG,UAAU;QACb,GAAG,eAAe,CAAC,IAAI,CAAC;KACzB,CAAC;AACJ,CAAC;AAED,SAAS,eAAe,CAAC,IAAa;IACpC,OAAO;QACL,GAAG,kBAAkB,CAAC,IAAI,CAAC;QAC3B,GAAG,wBAAwB,CAAC,IAAI,CAAC;KAClC,CAAC;AACJ,CAAC;AAED,SAAS,kBAAkB,CAAC,IAAa;IACvC,MAAM,SAAS,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;IACtC,OAAO,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAC,gBAAgB,EAAE,SAAS,EAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AACnE,CAAC;AAED,SAAS,aAAa,CAAC,MAAe;IACpC,OAAO,CACL,CAAC,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,SAAS,IAAI,MAAM,IAAK,MAA8B,CAAC,OAAO,KAAK,IAAI,CAClH,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"wrapOperations.js","sourceRoot":"","sources":["../../src/telemetry/wrapOperations.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"get-page-source.d.ts","sourceRoot":"","sources":["../../../src/tools/interactions/get-page-source.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgB,OAAO,EAAC,MAAM,SAAS,CAAC;AAOpD,MAAM,CAAC,OAAO,UAAU,aAAa,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CA0C3D"}
1
+ {"version":3,"file":"get-page-source.d.ts","sourceRoot":"","sources":["../../../src/tools/interactions/get-page-source.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgB,OAAO,EAAC,MAAM,SAAS,CAAC;AASpD,MAAM,CAAC,OAAO,UAAU,aAAa,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CA+C3D"}
@@ -1,20 +1,24 @@
1
1
  import { z } from 'zod';
2
2
  import { getPageSource as _getPageSource } from '../../command.js';
3
+ import { PAGE_SOURCE_INSPECTOR_URI } from '../../resources/page-source-inspector.js';
4
+ import { clientSupportsMcpApps, isMcpAppsEnabled } from '../../ui/mcp-apps.js';
3
5
  import { createUIResource, createPageSourceInspectorUI, addUIResourceToResponse } from '../../ui/mcp-ui-utils.js';
4
6
  import { resolveDriver, textResult, errorResult, toolErrorMessage } from '../tool-response.js';
5
7
  export default function getPageSource(server) {
8
+ const mcpAppsEnabled = isMcpAppsEnabled();
6
9
  const pageSourceSchema = z.object({
7
10
  sessionId: z.string().optional().describe('Session ID to target. If omitted, uses the active session.'),
8
11
  });
9
12
  server.addTool({
10
13
  name: 'appium_get_page_source',
11
14
  description: 'Get the page source (XML) from the current screen',
15
+ _meta: mcpAppsEnabled ? { ui: { resourceUri: PAGE_SOURCE_INSPECTOR_URI } } : undefined,
12
16
  parameters: pageSourceSchema,
13
17
  annotations: {
14
18
  readOnlyHint: true,
15
19
  openWorldHint: false,
16
20
  },
17
- execute: async (args, _context) => {
21
+ execute: async (args, context) => {
18
22
  const resolved = await resolveDriver(args.sessionId);
19
23
  if (!resolved.ok) {
20
24
  return resolved.result;
@@ -26,8 +30,13 @@ export default function getPageSource(server) {
26
30
  return errorResult('Page source is empty or null');
27
31
  }
28
32
  const textResponse = textResult('Page source retrieved successfully: \n' + '```xml ' + pageSource + '```');
33
+ // MCP Apps-capable clients fetch the static inspector once and pass
34
+ // this existing text result to it, avoiding a second copy of the XML.
35
+ if (mcpAppsEnabled && clientSupportsMcpApps(server, context)) {
36
+ return textResponse;
37
+ }
29
38
  // Add interactive page source inspector UI
30
- return addUIResourceToResponse(textResponse, () => createUIResource(`ui://appium-mcp/page-source-inspector/${Date.now()}`, createPageSourceInspectorUI(pageSource)));
39
+ return addUIResourceToResponse(textResponse, () => createUIResource(`${PAGE_SOURCE_INSPECTOR_URI}/${Date.now()}`, createPageSourceInspectorUI(pageSource)));
31
40
  }
32
41
  catch (err) {
33
42
  return errorResult(`Failed to get page source. Error: ${toolErrorMessage(err)}`);
@@ -1 +1 @@
1
- {"version":3,"file":"get-page-source.js","sourceRoot":"","sources":["../../../src/tools/interactions/get-page-source.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAEtB,OAAO,EAAC,aAAa,IAAI,cAAc,EAAC,MAAM,kBAAkB,CAAC;AACjE,OAAO,EAAC,gBAAgB,EAAE,2BAA2B,EAAE,uBAAuB,EAAC,MAAM,0BAA0B,CAAC;AAChH,OAAO,EAAC,aAAa,EAAE,UAAU,EAAE,WAAW,EAAE,gBAAgB,EAAC,MAAM,qBAAqB,CAAC;AAE7F,MAAM,CAAC,OAAO,UAAU,aAAa,CAAC,MAAe;IACnD,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC;QAChC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,4DAA4D,CAAC;KACxG,CAAC,CAAC;IACH,MAAM,CAAC,OAAO,CAAC;QACb,IAAI,EAAE,wBAAwB;QAC9B,WAAW,EAAE,mDAAmD;QAChE,UAAU,EAAE,gBAAgB;QAC5B,WAAW,EAAE;YACX,YAAY,EAAE,IAAI;YAClB,aAAa,EAAE,KAAK;SACrB;QACD,OAAO,EAAE,KAAK,EACZ,IAAsC,EACtC,QAA6C,EACrB,EAAE;YAC1B,MAAM,QAAQ,GAAG,MAAM,aAAa,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YACrD,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACjB,OAAO,QAAQ,CAAC,MAAM,CAAC;YACzB,CAAC;YACD,MAAM,EAAC,MAAM,EAAC,GAAG,QAAQ,CAAC;YAE1B,IAAI,CAAC;gBACH,MAAM,UAAU,GAAG,MAAM,cAAc,CAAC,MAAM,CAAC,CAAC;gBAChD,IAAI,CAAC,UAAU,EAAE,CAAC;oBAChB,OAAO,WAAW,CAAC,8BAA8B,CAAC,CAAC;gBACrD,CAAC;gBAED,MAAM,YAAY,GAAG,UAAU,CAAC,wCAAwC,GAAG,SAAS,GAAG,UAAU,GAAG,KAAK,CAAC,CAAC;gBAE3G,2CAA2C;gBAC3C,OAAO,uBAAuB,CAAC,YAAY,EAAE,GAAG,EAAE,CAChD,gBAAgB,CACd,yCAAyC,IAAI,CAAC,GAAG,EAAE,EAAE,EACrD,2BAA2B,CAAC,UAAU,CAAC,CACxC,CACF,CAAC;YACJ,CAAC;YAAC,OAAO,GAAY,EAAE,CAAC;gBACtB,OAAO,WAAW,CAAC,qCAAqC,gBAAgB,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACnF,CAAC;QACH,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"get-page-source.js","sourceRoot":"","sources":["../../../src/tools/interactions/get-page-source.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAEtB,OAAO,EAAC,aAAa,IAAI,cAAc,EAAC,MAAM,kBAAkB,CAAC;AACjE,OAAO,EAAC,yBAAyB,EAAC,MAAM,0CAA0C,CAAC;AACnF,OAAO,EAAC,qBAAqB,EAAE,gBAAgB,EAAC,MAAM,sBAAsB,CAAC;AAC7E,OAAO,EAAC,gBAAgB,EAAE,2BAA2B,EAAE,uBAAuB,EAAC,MAAM,0BAA0B,CAAC;AAChH,OAAO,EAAC,aAAa,EAAE,UAAU,EAAE,WAAW,EAAE,gBAAgB,EAAC,MAAM,qBAAqB,CAAC;AAE7F,MAAM,CAAC,OAAO,UAAU,aAAa,CAAC,MAAe;IACnD,MAAM,cAAc,GAAG,gBAAgB,EAAE,CAAC;IAC1C,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC;QAChC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,4DAA4D,CAAC;KACxG,CAAC,CAAC;IACH,MAAM,CAAC,OAAO,CAAC;QACb,IAAI,EAAE,wBAAwB;QAC9B,WAAW,EAAE,mDAAmD;QAChE,KAAK,EAAE,cAAc,CAAC,CAAC,CAAC,EAAC,EAAE,EAAE,EAAC,WAAW,EAAE,yBAAyB,EAAC,EAAC,CAAC,CAAC,CAAC,SAAS;QAClF,UAAU,EAAE,gBAAgB;QAC5B,WAAW,EAAE;YACX,YAAY,EAAE,IAAI;YAClB,aAAa,EAAE,KAAK;SACrB;QACD,OAAO,EAAE,KAAK,EACZ,IAAsC,EACtC,OAA4C,EACpB,EAAE;YAC1B,MAAM,QAAQ,GAAG,MAAM,aAAa,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YACrD,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACjB,OAAO,QAAQ,CAAC,MAAM,CAAC;YACzB,CAAC;YACD,MAAM,EAAC,MAAM,EAAC,GAAG,QAAQ,CAAC;YAE1B,IAAI,CAAC;gBACH,MAAM,UAAU,GAAG,MAAM,cAAc,CAAC,MAAM,CAAC,CAAC;gBAChD,IAAI,CAAC,UAAU,EAAE,CAAC;oBAChB,OAAO,WAAW,CAAC,8BAA8B,CAAC,CAAC;gBACrD,CAAC;gBAED,MAAM,YAAY,GAAG,UAAU,CAAC,wCAAwC,GAAG,SAAS,GAAG,UAAU,GAAG,KAAK,CAAC,CAAC;gBAE3G,oEAAoE;gBACpE,sEAAsE;gBACtE,IAAI,cAAc,IAAI,qBAAqB,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;oBAC7D,OAAO,YAAY,CAAC;gBACtB,CAAC;gBAED,2CAA2C;gBAC3C,OAAO,uBAAuB,CAAC,YAAY,EAAE,GAAG,EAAE,CAChD,gBAAgB,CAAC,GAAG,yBAAyB,IAAI,IAAI,CAAC,GAAG,EAAE,EAAE,EAAE,2BAA2B,CAAC,UAAU,CAAC,CAAC,CACxG,CAAC;YACJ,CAAC;YAAC,OAAO,GAAY,EAAE,CAAC;gBACtB,OAAO,WAAW,CAAC,qCAAqC,gBAAgB,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACnF,CAAC;QACH,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,15 @@
1
+ import type { FastMCP } from 'fastmcp';
2
+ export declare const MCP_APPS_EXTENSION_ID = "io.modelcontextprotocol/ui";
3
+ export declare const MCP_APP_MIME_TYPE = "text/html;profile=mcp-app";
4
+ export declare function isUIEnabled(): boolean;
5
+ /**
6
+ * MCP Apps are enabled by default when UI is enabled. Set
7
+ * APPIUM_MCP_APPS_ENABLED=false or 0 to force the embedded UI compatibility
8
+ * path for clients whose MCP Apps renderer is unreliable.
9
+ */
10
+ export declare function isMcpAppsEnabled(): boolean;
11
+ export declare function supportsMcpAppsCapability(capabilities: unknown): boolean;
12
+ export declare function clientSupportsMcpApps(server: Pick<FastMCP, 'sessions'>, context: {
13
+ sessionId?: string;
14
+ } | undefined): boolean;
15
+ //# sourceMappingURL=mcp-apps.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-apps.d.ts","sourceRoot":"","sources":["../../src/ui/mcp-apps.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,SAAS,CAAC;AAErC,eAAO,MAAM,qBAAqB,+BAA+B,CAAC;AAClE,eAAO,MAAM,iBAAiB,8BAA8B,CAAC;AAE7D,wBAAgB,WAAW,IAAI,OAAO,CAErC;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,IAAI,OAAO,CAG1C;AAED,wBAAgB,yBAAyB,CAAC,YAAY,EAAE,OAAO,GAAG,OAAO,CAiBxE;AAED,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,IAAI,CAAC,OAAO,EAAE,UAAU,CAAC,EACjC,OAAO,EAAE;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAC,GAAG,SAAS,GACxC,OAAO,CAST"}
@@ -0,0 +1,44 @@
1
+ export const MCP_APPS_EXTENSION_ID = 'io.modelcontextprotocol/ui';
2
+ export const MCP_APP_MIME_TYPE = 'text/html;profile=mcp-app';
3
+ export function isUIEnabled() {
4
+ return process.env.NO_UI !== 'true' && process.env.NO_UI !== '1';
5
+ }
6
+ /**
7
+ * MCP Apps are enabled by default when UI is enabled. Set
8
+ * APPIUM_MCP_APPS_ENABLED=false or 0 to force the embedded UI compatibility
9
+ * path for clients whose MCP Apps renderer is unreliable.
10
+ */
11
+ export function isMcpAppsEnabled() {
12
+ const configuredValue = process.env.APPIUM_MCP_APPS_ENABLED;
13
+ return isUIEnabled() && configuredValue !== 'false' && configuredValue !== '0';
14
+ }
15
+ export function supportsMcpAppsCapability(capabilities) {
16
+ if (!isRecord(capabilities) || !isRecord(capabilities.extensions)) {
17
+ return false;
18
+ }
19
+ const uiCapability = capabilities.extensions[MCP_APPS_EXTENSION_ID];
20
+ if (!isRecord(uiCapability) || !Array.isArray(uiCapability.mimeTypes)) {
21
+ return false;
22
+ }
23
+ return uiCapability.mimeTypes.some((mimeType) => {
24
+ if (typeof mimeType !== 'string') {
25
+ return false;
26
+ }
27
+ return normalizeMimeType(mimeType) === MCP_APP_MIME_TYPE;
28
+ });
29
+ }
30
+ export function clientSupportsMcpApps(server, context) {
31
+ const session = context?.sessionId !== undefined
32
+ ? server.sessions.find((candidate) => candidate.sessionId === context.sessionId)
33
+ : server.sessions.length === 1
34
+ ? server.sessions[0]
35
+ : undefined;
36
+ return supportsMcpAppsCapability(session?.clientCapabilities);
37
+ }
38
+ function isRecord(value) {
39
+ return typeof value === 'object' && value !== null;
40
+ }
41
+ function normalizeMimeType(mimeType) {
42
+ return mimeType.replace(/[\s"]/g, '').toLowerCase();
43
+ }
44
+ //# sourceMappingURL=mcp-apps.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-apps.js","sourceRoot":"","sources":["../../src/ui/mcp-apps.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,qBAAqB,GAAG,4BAA4B,CAAC;AAClE,MAAM,CAAC,MAAM,iBAAiB,GAAG,2BAA2B,CAAC;AAE7D,MAAM,UAAU,WAAW;IACzB,OAAO,OAAO,CAAC,GAAG,CAAC,KAAK,KAAK,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,KAAK,GAAG,CAAC;AACnE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB;IAC9B,MAAM,eAAe,GAAG,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC;IAC5D,OAAO,WAAW,EAAE,IAAI,eAAe,KAAK,OAAO,IAAI,eAAe,KAAK,GAAG,CAAC;AACjF,CAAC;AAED,MAAM,UAAU,yBAAyB,CAAC,YAAqB;IAC7D,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,UAAU,CAAC,EAAE,CAAC;QAClE,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,YAAY,GAAG,YAAY,CAAC,UAAU,CAAC,qBAAqB,CAAC,CAAC;IACpE,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,SAAS,CAAC,EAAE,CAAC;QACtE,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,YAAY,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE;QAC9C,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;YACjC,OAAO,KAAK,CAAC;QACf,CAAC;QAED,OAAO,iBAAiB,CAAC,QAAQ,CAAC,KAAK,iBAAiB,CAAC;IAC3D,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,qBAAqB,CACnC,MAAiC,EACjC,OAAyC;IAEzC,MAAM,OAAO,GACX,OAAO,EAAE,SAAS,KAAK,SAAS;QAC9B,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,SAAS,KAAK,OAAO,CAAC,SAAS,CAAC;QAChF,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC;YAC5B,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;YACpB,CAAC,CAAC,SAAS,CAAC;IAElB,OAAO,yBAAyB,CAAC,OAAO,EAAE,kBAAkB,CAAC,CAAC;AAChE,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,CAAC;AACrD,CAAC;AAED,SAAS,iBAAiB,CAAC,QAAgB;IACzC,OAAO,QAAQ,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;AACtD,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Static MCP App used by appium_get_page_source.
3
+ *
4
+ * The page source is read from the tool's existing text result, so it is sent
5
+ * to the client only once instead of being duplicated inside inline HTML.
6
+ */
7
+ export declare function createPageSourceInspectorAppUI(): string;
8
+ //# sourceMappingURL=page-source-inspector-app.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-source-inspector-app.d.ts","sourceRoot":"","sources":["../../src/ui/page-source-inspector-app.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,wBAAgB,8BAA8B,IAAI,MAAM,CAkPvD"}
@@ -0,0 +1,250 @@
1
+ /**
2
+ * Static MCP App used by appium_get_page_source.
3
+ *
4
+ * The page source is read from the tool's existing text result, so it is sent
5
+ * to the client only once instead of being duplicated inside inline HTML.
6
+ */
7
+ export function createPageSourceInspectorAppUI() {
8
+ return `
9
+ <!DOCTYPE html>
10
+ <html lang="en">
11
+ <head>
12
+ <meta charset="UTF-8">
13
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
14
+ <title>Page Source Inspector</title>
15
+ <style>
16
+ * { box-sizing: border-box; }
17
+ body {
18
+ margin: 0;
19
+ overflow: hidden;
20
+ background: #1e1e1e;
21
+ color: #d4d4d4;
22
+ font-family: Monaco, Menlo, "Courier New", monospace;
23
+ }
24
+ .toolbar {
25
+ display: flex;
26
+ align-items: center;
27
+ justify-content: space-between;
28
+ gap: 12px;
29
+ padding: 12px 16px;
30
+ border-bottom: 1px solid #3e3e3e;
31
+ background: #2d2d2d;
32
+ }
33
+ .toolbar-left, .toolbar-right {
34
+ display: flex;
35
+ align-items: center;
36
+ gap: 8px;
37
+ }
38
+ .title { font-size: 14px; font-weight: 500; }
39
+ .info { color: #999; font-size: 12px; }
40
+ .btn {
41
+ padding: 6px 12px;
42
+ border: 0;
43
+ border-radius: 4px;
44
+ background: #007aff;
45
+ color: #fff;
46
+ cursor: pointer;
47
+ font: 13px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
48
+ }
49
+ .btn:hover { background: #0056b3; }
50
+ .btn-secondary { background: #444; }
51
+ .btn-secondary:hover { background: #555; }
52
+ .search-box {
53
+ width: 200px;
54
+ padding: 6px 12px;
55
+ border: 1px solid #555;
56
+ border-radius: 4px;
57
+ outline: none;
58
+ background: #3e3e3e;
59
+ color: #d4d4d4;
60
+ font-size: 13px;
61
+ }
62
+ .search-box:focus { border-color: #007aff; }
63
+ .viewer {
64
+ height: calc(100vh - 50px);
65
+ overflow: auto;
66
+ padding: 16px;
67
+ }
68
+ .xml-content {
69
+ margin: 0;
70
+ white-space: pre;
71
+ color: #d4d4d4;
72
+ font-size: 13px;
73
+ line-height: 1.6;
74
+ }
75
+ mark {
76
+ border-radius: 2px;
77
+ background: #ffd54f;
78
+ color: #1e1e1e;
79
+ }
80
+ .empty {
81
+ color: #999;
82
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
83
+ }
84
+ </style>
85
+ </head>
86
+ <body>
87
+ <div class="toolbar">
88
+ <div class="toolbar-left">
89
+ <span class="title">📄 Page Source Inspector</span>
90
+ <span class="info" id="sourceInfo">Waiting for page source…</span>
91
+ </div>
92
+ <div class="toolbar-right">
93
+ <input type="text" class="search-box" id="searchBox" placeholder="Search…">
94
+ <button class="btn btn-secondary" id="copyButton">Copy</button>
95
+ <button class="btn btn-secondary" id="formatButton">Format</button>
96
+ <button class="btn" id="generateButton">Generate Locators</button>
97
+ </div>
98
+ </div>
99
+ <div class="viewer">
100
+ <pre class="xml-content empty" id="xmlContent">Waiting for page source…</pre>
101
+ </div>
102
+ <script>
103
+ (() => {
104
+ const PAGE_SOURCE_PREFIX = 'Page source retrieved successfully: \\n\\\`\\\`\\\`xml ';
105
+ const xmlContent = document.getElementById('xmlContent');
106
+ const sourceInfo = document.getElementById('sourceInfo');
107
+ const searchBox = document.getElementById('searchBox');
108
+ let source = '';
109
+ let nextRequestId = 1;
110
+ const pendingRequests = new Map();
111
+
112
+ function sendRequest(method, params) {
113
+ const id = nextRequestId++;
114
+ window.parent.postMessage({jsonrpc: '2.0', id, method, params}, '*');
115
+ return new Promise((resolve, reject) => pendingRequests.set(id, {resolve, reject}));
116
+ }
117
+
118
+ function sendNotification(method, params) {
119
+ window.parent.postMessage({jsonrpc: '2.0', method, params}, '*');
120
+ }
121
+
122
+ function sourceFromToolResult(result) {
123
+ const textBlock = result && Array.isArray(result.content)
124
+ ? result.content.find((item) => item && item.type === 'text' && typeof item.text === 'string')
125
+ : undefined;
126
+ if (!textBlock) {
127
+ return '';
128
+ }
129
+
130
+ let text = textBlock.text;
131
+ if (text.startsWith(PAGE_SOURCE_PREFIX) && text.endsWith('\`\`\`')) {
132
+ text = text.slice(PAGE_SOURCE_PREFIX.length, -3);
133
+ }
134
+ return text;
135
+ }
136
+
137
+ function appendHighlightedText(container, text, query) {
138
+ const lowerText = text.toLowerCase();
139
+ const lowerQuery = query.toLowerCase();
140
+ let position = 0;
141
+
142
+ while (position < text.length) {
143
+ const matchIndex = lowerText.indexOf(lowerQuery, position);
144
+ if (matchIndex === -1) {
145
+ container.appendChild(document.createTextNode(text.slice(position)));
146
+ return;
147
+ }
148
+ if (matchIndex > position) {
149
+ container.appendChild(document.createTextNode(text.slice(position, matchIndex)));
150
+ }
151
+ const mark = document.createElement('mark');
152
+ mark.textContent = text.slice(matchIndex, matchIndex + query.length);
153
+ container.appendChild(mark);
154
+ position = matchIndex + query.length;
155
+ }
156
+ }
157
+
158
+ function renderSource() {
159
+ const query = searchBox.value.trim();
160
+ xmlContent.textContent = '';
161
+ xmlContent.classList.toggle('empty', source.length === 0);
162
+ if (!source) {
163
+ xmlContent.textContent = 'No page source was returned.';
164
+ } else if (!query) {
165
+ xmlContent.textContent = source;
166
+ } else {
167
+ appendHighlightedText(xmlContent, source, query);
168
+ }
169
+ sourceInfo.textContent = source.length + ' characters';
170
+ }
171
+
172
+ function setSource(nextSource) {
173
+ source = nextSource;
174
+ renderSource();
175
+ }
176
+
177
+ function formatXML() {
178
+ if (!source) return;
179
+ try {
180
+ const parser = new DOMParser();
181
+ const documentNode = parser.parseFromString(source, 'text/xml');
182
+ if (documentNode.querySelector('parsererror')) {
183
+ throw new Error('Invalid XML');
184
+ }
185
+ source = new XMLSerializer().serializeToString(documentNode);
186
+ renderSource();
187
+ } catch {
188
+ window.alert('Failed to format XML');
189
+ }
190
+ }
191
+
192
+ async function copyToClipboard() {
193
+ if (!source) return;
194
+ try {
195
+ await navigator.clipboard.writeText(source);
196
+ } catch {
197
+ window.alert('Failed to copy page source');
198
+ }
199
+ }
200
+
201
+ async function generateLocators() {
202
+ try {
203
+ await sendRequest('tools/call', {name: 'generate_locators', arguments: {}});
204
+ } catch {
205
+ window.alert('Failed to generate locators');
206
+ }
207
+ }
208
+
209
+ window.addEventListener('message', (event) => {
210
+ if (event.source !== window.parent) return;
211
+ const message = event.data;
212
+ if (!message || message.jsonrpc !== '2.0') return;
213
+
214
+ if (message.id !== undefined && pendingRequests.has(message.id)) {
215
+ const pending = pendingRequests.get(message.id);
216
+ pendingRequests.delete(message.id);
217
+ if (message.error) {
218
+ pending.reject(new Error(message.error.message || 'MCP App request failed'));
219
+ } else {
220
+ pending.resolve(message.result);
221
+ }
222
+ return;
223
+ }
224
+
225
+ if (message.method === 'ui/notifications/tool-result') {
226
+ setSource(sourceFromToolResult(message.params));
227
+ }
228
+ });
229
+
230
+ searchBox.addEventListener('input', renderSource);
231
+ document.getElementById('copyButton').addEventListener('click', copyToClipboard);
232
+ document.getElementById('formatButton').addEventListener('click', formatXML);
233
+ document.getElementById('generateButton').addEventListener('click', generateLocators);
234
+
235
+ sendRequest('ui/initialize', {
236
+ protocolVersion: '2026-01-26',
237
+ appInfo: {name: 'Appium Page Source Inspector', version: '1.0.0'},
238
+ appCapabilities: {availableDisplayModes: ['inline', 'fullscreen']},
239
+ })
240
+ .then(() => sendNotification('ui/notifications/initialized', {}))
241
+ .catch(() => {
242
+ sourceInfo.textContent = 'Unable to initialize inspector';
243
+ });
244
+ })();
245
+ </script>
246
+ </body>
247
+ </html>
248
+ `;
249
+ }
250
+ //# sourceMappingURL=page-source-inspector-app.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-source-inspector-app.js","sourceRoot":"","sources":["../../src/ui/page-source-inspector-app.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,UAAU,8BAA8B;IAC5C,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgPN,CAAC;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "appium-mcp",
3
- "version": "1.88.5",
3
+ "version": "1.90.0",
4
4
  "description": "Intelligent MCP server providing AI assistants with powerful tools and resources for Appium mobile automation",
5
5
  "bugs": {
6
6
  "url": "https://github.com/appium/appium-mcp/issues"
package/server.json CHANGED
@@ -3,12 +3,12 @@
3
3
  "name": "io.github.appium/appium-mcp",
4
4
  "title": "MCP Appium - Mobile Development and Automation Server",
5
5
  "description": "MCP server for Appium mobile automation on iOS and Android devices with test creation tools.",
6
- "version": "1.88.5",
6
+ "version": "1.90.0",
7
7
  "packages": [
8
8
  {
9
9
  "registryType": "npm",
10
10
  "identifier": "appium-mcp",
11
- "version": "1.88.5",
11
+ "version": "1.90.0",
12
12
  "transport": {
13
13
  "type": "stdio"
14
14
  }
@@ -1,8 +1,13 @@
1
1
  import log from '../logger.js';
2
+ import {isMcpAppsEnabled} from '../ui/mcp-apps.js';
2
3
  // Export all resources
3
4
  import javaTemplatesResource from './java/template.js';
5
+ import pageSourceInspectorResource from './page-source-inspector.js';
4
6
 
5
7
  export default function registerResources(server: any) {
6
8
  javaTemplatesResource(server);
9
+ if (isMcpAppsEnabled()) {
10
+ pageSourceInspectorResource(server);
11
+ }
7
12
  log.info('All resources registered');
8
13
  }
@@ -0,0 +1,18 @@
1
+ import type {FastMCP} from 'fastmcp';
2
+
3
+ import {MCP_APP_MIME_TYPE} from '../ui/mcp-apps.js';
4
+ import {createPageSourceInspectorAppUI} from '../ui/page-source-inspector-app.js';
5
+
6
+ export const PAGE_SOURCE_INSPECTOR_URI = 'ui://appium-mcp/page-source-inspector';
7
+
8
+ export default function pageSourceInspectorResource(server: FastMCP): void {
9
+ server.addResource({
10
+ uri: PAGE_SOURCE_INSPECTOR_URI,
11
+ name: 'Appium Page Source Inspector',
12
+ description: 'Interactive inspector for appium_get_page_source results',
13
+ mimeType: MCP_APP_MIME_TYPE,
14
+ async load() {
15
+ return {text: createPageSourceInspectorAppUI()};
16
+ },
17
+ });
18
+ }
@@ -21,6 +21,8 @@ type PromptLoad = NonNullable<PromptDef['load']>;
21
21
  type ResourceLoad = NonNullable<ResourceDef['load']>;
22
22
  type ResourceTemplateLoad = NonNullable<ResourceTemplateDef['load']>;
23
23
 
24
+ const SAFE_TOOL_CONTENT_TYPES = new Set(['audio', 'image', 'resource', 'resource_link', 'text']);
25
+
24
26
  /**
25
27
  * Installs telemetry wrappers on the given FastMCP server instance. This should be
26
28
  * called early in the server setup process, before registering any tools, prompts,
@@ -77,15 +79,98 @@ export function wrapToolWithTelemetry(toolDef: ToolDef): ToolDef {
77
79
  execute: async (args, context) =>
78
80
  withSpan(`tools/call ${toolName}`, toolAttributes(toolName, args), async () => {
79
81
  const result = await execute(args, context);
82
+ const span = getActiveSpan();
83
+ span?.setAttributes(safeToolResultSizeAttributes(result));
80
84
  if (isErrorResult(result)) {
81
- getActiveSpan()?.setStatus({code: SpanStatusCode.ERROR});
82
- getActiveSpan()?.setAttribute('mcp.tool.result.is_error', true);
85
+ span?.setStatus({code: SpanStatusCode.ERROR});
86
+ span?.setAttribute('mcp.tool.result.is_error', true);
83
87
  }
84
88
  return result;
85
89
  }),
86
90
  };
87
91
  }
88
92
 
93
+ /**
94
+ * Builds payload-free size attributes for a tool result.
95
+ *
96
+ * This intentionally counts known MCP content fields directly instead of
97
+ * serializing the whole result. Large page sources, UI HTML, and base64 data
98
+ * therefore are not copied or recorded in telemetry.
99
+ */
100
+ export function safeToolResultSizeAttributes(result: unknown): Record<string, number | string[]> {
101
+ if (typeof result === 'string') {
102
+ return {'mcp.tool.result.text_chars': result.length};
103
+ }
104
+ if (!isRecord(result) || !Array.isArray(result.content)) {
105
+ return {};
106
+ }
107
+
108
+ const contentTypes = new Set<string>();
109
+ let textChars = 0;
110
+ let resourceCount = 0;
111
+ let resourceTextChars = 0;
112
+ let imageCount = 0;
113
+ let audioCount = 0;
114
+ let base64Chars = 0;
115
+ let base64BytesEstimate = 0;
116
+
117
+ for (const content of result.content) {
118
+ if (!isRecord(content)) {
119
+ contentTypes.add('other');
120
+ continue;
121
+ }
122
+
123
+ const contentType =
124
+ typeof content.type === 'string' && SAFE_TOOL_CONTENT_TYPES.has(content.type) ? content.type : 'other';
125
+ contentTypes.add(contentType);
126
+
127
+ if (contentType === 'text' && typeof content.text === 'string') {
128
+ textChars += content.text.length;
129
+ continue;
130
+ }
131
+
132
+ if (contentType === 'image' || contentType === 'audio') {
133
+ if (contentType === 'image') {
134
+ imageCount += 1;
135
+ } else {
136
+ audioCount += 1;
137
+ }
138
+ if (typeof content.data === 'string') {
139
+ base64Chars += content.data.length;
140
+ base64BytesEstimate += estimateBase64DecodedBytes(content.data);
141
+ }
142
+ continue;
143
+ }
144
+
145
+ if (contentType === 'resource') {
146
+ resourceCount += 1;
147
+ if (isRecord(content.resource)) {
148
+ if (typeof content.resource.text === 'string') {
149
+ resourceTextChars += content.resource.text.length;
150
+ }
151
+ if (typeof content.resource.blob === 'string') {
152
+ base64Chars += content.resource.blob.length;
153
+ base64BytesEstimate += estimateBase64DecodedBytes(content.resource.blob);
154
+ }
155
+ }
156
+ }
157
+ }
158
+
159
+ const attributes: Record<string, number | string[]> = {
160
+ 'mcp.tool.result.content_count': result.content.length,
161
+ 'mcp.tool.result.content_types': [...contentTypes].sort(),
162
+ 'mcp.tool.result.text_chars': textChars,
163
+ 'mcp.tool.result.resource_count': resourceCount,
164
+ 'mcp.tool.result.resource_text_chars': resourceTextChars,
165
+ 'mcp.tool.result.image_count': imageCount,
166
+ 'mcp.tool.result.audio_count': audioCount,
167
+ 'mcp.tool.result.base64_chars': base64Chars,
168
+ 'mcp.tool.result.base64_bytes_estimate': base64BytesEstimate,
169
+ };
170
+
171
+ return attributes;
172
+ }
173
+
89
174
  export function safeInputValueAttributes(args: unknown): Record<string, string | number | boolean | string[]> {
90
175
  if (!isArgumentValueTelemetryEnabled()) {
91
176
  return {};
@@ -213,3 +298,25 @@ function isErrorResult(result: unknown): boolean {
213
298
  !!result && typeof result === 'object' && 'isError' in result && (result as {isError?: unknown}).isError === true
214
299
  );
215
300
  }
301
+
302
+ function isRecord(value: unknown): value is Record<string, unknown> {
303
+ return !!value && typeof value === 'object' && !Array.isArray(value);
304
+ }
305
+
306
+ function estimateBase64DecodedBytes(value: string): number {
307
+ const commaIndex = value.indexOf(',');
308
+ const hasDataUriPrefix = commaIndex >= 7 && value.slice(commaIndex - 7, commaIndex).toLowerCase() === ';base64';
309
+ const contentStart = hasDataUriPrefix ? commaIndex + 1 : 0;
310
+ const encodedLength = value.length - contentStart;
311
+ if (encodedLength <= 0) {
312
+ return 0;
313
+ }
314
+
315
+ let padding = 0;
316
+ if (value.endsWith('==')) {
317
+ padding = 2;
318
+ } else if (value.endsWith('=')) {
319
+ padding = 1;
320
+ }
321
+ return Math.max(0, Math.floor((encodedLength * 3) / 4) - padding);
322
+ }
@@ -2,16 +2,20 @@ import type {ContentResult, FastMCP} from 'fastmcp';
2
2
  import {z} from 'zod';
3
3
 
4
4
  import {getPageSource as _getPageSource} from '../../command.js';
5
+ import {PAGE_SOURCE_INSPECTOR_URI} from '../../resources/page-source-inspector.js';
6
+ import {clientSupportsMcpApps, isMcpAppsEnabled} from '../../ui/mcp-apps.js';
5
7
  import {createUIResource, createPageSourceInspectorUI, addUIResourceToResponse} from '../../ui/mcp-ui-utils.js';
6
8
  import {resolveDriver, textResult, errorResult, toolErrorMessage} from '../tool-response.js';
7
9
 
8
10
  export default function getPageSource(server: FastMCP): void {
11
+ const mcpAppsEnabled = isMcpAppsEnabled();
9
12
  const pageSourceSchema = z.object({
10
13
  sessionId: z.string().optional().describe('Session ID to target. If omitted, uses the active session.'),
11
14
  });
12
15
  server.addTool({
13
16
  name: 'appium_get_page_source',
14
17
  description: 'Get the page source (XML) from the current screen',
18
+ _meta: mcpAppsEnabled ? {ui: {resourceUri: PAGE_SOURCE_INSPECTOR_URI}} : undefined,
15
19
  parameters: pageSourceSchema,
16
20
  annotations: {
17
21
  readOnlyHint: true,
@@ -19,7 +23,7 @@ export default function getPageSource(server: FastMCP): void {
19
23
  },
20
24
  execute: async (
21
25
  args: z.infer<typeof pageSourceSchema>,
22
- _context: Record<string, unknown> | undefined,
26
+ context: Record<string, unknown> | undefined,
23
27
  ): Promise<ContentResult> => {
24
28
  const resolved = await resolveDriver(args.sessionId);
25
29
  if (!resolved.ok) {
@@ -35,12 +39,15 @@ export default function getPageSource(server: FastMCP): void {
35
39
 
36
40
  const textResponse = textResult('Page source retrieved successfully: \n' + '```xml ' + pageSource + '```');
37
41
 
42
+ // MCP Apps-capable clients fetch the static inspector once and pass
43
+ // this existing text result to it, avoiding a second copy of the XML.
44
+ if (mcpAppsEnabled && clientSupportsMcpApps(server, context)) {
45
+ return textResponse;
46
+ }
47
+
38
48
  // Add interactive page source inspector UI
39
49
  return addUIResourceToResponse(textResponse, () =>
40
- createUIResource(
41
- `ui://appium-mcp/page-source-inspector/${Date.now()}`,
42
- createPageSourceInspectorUI(pageSource),
43
- ),
50
+ createUIResource(`${PAGE_SOURCE_INSPECTOR_URI}/${Date.now()}`, createPageSourceInspectorUI(pageSource)),
44
51
  );
45
52
  } catch (err: unknown) {
46
53
  return errorResult(`Failed to get page source. Error: ${toolErrorMessage(err)}`);
@@ -0,0 +1,59 @@
1
+ import type {FastMCP} from 'fastmcp';
2
+
3
+ export const MCP_APPS_EXTENSION_ID = 'io.modelcontextprotocol/ui';
4
+ export const MCP_APP_MIME_TYPE = 'text/html;profile=mcp-app';
5
+
6
+ export function isUIEnabled(): boolean {
7
+ return process.env.NO_UI !== 'true' && process.env.NO_UI !== '1';
8
+ }
9
+
10
+ /**
11
+ * MCP Apps are enabled by default when UI is enabled. Set
12
+ * APPIUM_MCP_APPS_ENABLED=false or 0 to force the embedded UI compatibility
13
+ * path for clients whose MCP Apps renderer is unreliable.
14
+ */
15
+ export function isMcpAppsEnabled(): boolean {
16
+ const configuredValue = process.env.APPIUM_MCP_APPS_ENABLED;
17
+ return isUIEnabled() && configuredValue !== 'false' && configuredValue !== '0';
18
+ }
19
+
20
+ export function supportsMcpAppsCapability(capabilities: unknown): boolean {
21
+ if (!isRecord(capabilities) || !isRecord(capabilities.extensions)) {
22
+ return false;
23
+ }
24
+
25
+ const uiCapability = capabilities.extensions[MCP_APPS_EXTENSION_ID];
26
+ if (!isRecord(uiCapability) || !Array.isArray(uiCapability.mimeTypes)) {
27
+ return false;
28
+ }
29
+
30
+ return uiCapability.mimeTypes.some((mimeType) => {
31
+ if (typeof mimeType !== 'string') {
32
+ return false;
33
+ }
34
+
35
+ return normalizeMimeType(mimeType) === MCP_APP_MIME_TYPE;
36
+ });
37
+ }
38
+
39
+ export function clientSupportsMcpApps(
40
+ server: Pick<FastMCP, 'sessions'>,
41
+ context: {sessionId?: string} | undefined,
42
+ ): boolean {
43
+ const session =
44
+ context?.sessionId !== undefined
45
+ ? server.sessions.find((candidate) => candidate.sessionId === context.sessionId)
46
+ : server.sessions.length === 1
47
+ ? server.sessions[0]
48
+ : undefined;
49
+
50
+ return supportsMcpAppsCapability(session?.clientCapabilities);
51
+ }
52
+
53
+ function isRecord(value: unknown): value is Record<string, unknown> {
54
+ return typeof value === 'object' && value !== null;
55
+ }
56
+
57
+ function normalizeMimeType(mimeType: string): string {
58
+ return mimeType.replace(/[\s"]/g, '').toLowerCase();
59
+ }
@@ -0,0 +1,249 @@
1
+ /**
2
+ * Static MCP App used by appium_get_page_source.
3
+ *
4
+ * The page source is read from the tool's existing text result, so it is sent
5
+ * to the client only once instead of being duplicated inside inline HTML.
6
+ */
7
+ export function createPageSourceInspectorAppUI(): string {
8
+ return `
9
+ <!DOCTYPE html>
10
+ <html lang="en">
11
+ <head>
12
+ <meta charset="UTF-8">
13
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
14
+ <title>Page Source Inspector</title>
15
+ <style>
16
+ * { box-sizing: border-box; }
17
+ body {
18
+ margin: 0;
19
+ overflow: hidden;
20
+ background: #1e1e1e;
21
+ color: #d4d4d4;
22
+ font-family: Monaco, Menlo, "Courier New", monospace;
23
+ }
24
+ .toolbar {
25
+ display: flex;
26
+ align-items: center;
27
+ justify-content: space-between;
28
+ gap: 12px;
29
+ padding: 12px 16px;
30
+ border-bottom: 1px solid #3e3e3e;
31
+ background: #2d2d2d;
32
+ }
33
+ .toolbar-left, .toolbar-right {
34
+ display: flex;
35
+ align-items: center;
36
+ gap: 8px;
37
+ }
38
+ .title { font-size: 14px; font-weight: 500; }
39
+ .info { color: #999; font-size: 12px; }
40
+ .btn {
41
+ padding: 6px 12px;
42
+ border: 0;
43
+ border-radius: 4px;
44
+ background: #007aff;
45
+ color: #fff;
46
+ cursor: pointer;
47
+ font: 13px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
48
+ }
49
+ .btn:hover { background: #0056b3; }
50
+ .btn-secondary { background: #444; }
51
+ .btn-secondary:hover { background: #555; }
52
+ .search-box {
53
+ width: 200px;
54
+ padding: 6px 12px;
55
+ border: 1px solid #555;
56
+ border-radius: 4px;
57
+ outline: none;
58
+ background: #3e3e3e;
59
+ color: #d4d4d4;
60
+ font-size: 13px;
61
+ }
62
+ .search-box:focus { border-color: #007aff; }
63
+ .viewer {
64
+ height: calc(100vh - 50px);
65
+ overflow: auto;
66
+ padding: 16px;
67
+ }
68
+ .xml-content {
69
+ margin: 0;
70
+ white-space: pre;
71
+ color: #d4d4d4;
72
+ font-size: 13px;
73
+ line-height: 1.6;
74
+ }
75
+ mark {
76
+ border-radius: 2px;
77
+ background: #ffd54f;
78
+ color: #1e1e1e;
79
+ }
80
+ .empty {
81
+ color: #999;
82
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
83
+ }
84
+ </style>
85
+ </head>
86
+ <body>
87
+ <div class="toolbar">
88
+ <div class="toolbar-left">
89
+ <span class="title">📄 Page Source Inspector</span>
90
+ <span class="info" id="sourceInfo">Waiting for page source…</span>
91
+ </div>
92
+ <div class="toolbar-right">
93
+ <input type="text" class="search-box" id="searchBox" placeholder="Search…">
94
+ <button class="btn btn-secondary" id="copyButton">Copy</button>
95
+ <button class="btn btn-secondary" id="formatButton">Format</button>
96
+ <button class="btn" id="generateButton">Generate Locators</button>
97
+ </div>
98
+ </div>
99
+ <div class="viewer">
100
+ <pre class="xml-content empty" id="xmlContent">Waiting for page source…</pre>
101
+ </div>
102
+ <script>
103
+ (() => {
104
+ const PAGE_SOURCE_PREFIX = 'Page source retrieved successfully: \\n\\\`\\\`\\\`xml ';
105
+ const xmlContent = document.getElementById('xmlContent');
106
+ const sourceInfo = document.getElementById('sourceInfo');
107
+ const searchBox = document.getElementById('searchBox');
108
+ let source = '';
109
+ let nextRequestId = 1;
110
+ const pendingRequests = new Map();
111
+
112
+ function sendRequest(method, params) {
113
+ const id = nextRequestId++;
114
+ window.parent.postMessage({jsonrpc: '2.0', id, method, params}, '*');
115
+ return new Promise((resolve, reject) => pendingRequests.set(id, {resolve, reject}));
116
+ }
117
+
118
+ function sendNotification(method, params) {
119
+ window.parent.postMessage({jsonrpc: '2.0', method, params}, '*');
120
+ }
121
+
122
+ function sourceFromToolResult(result) {
123
+ const textBlock = result && Array.isArray(result.content)
124
+ ? result.content.find((item) => item && item.type === 'text' && typeof item.text === 'string')
125
+ : undefined;
126
+ if (!textBlock) {
127
+ return '';
128
+ }
129
+
130
+ let text = textBlock.text;
131
+ if (text.startsWith(PAGE_SOURCE_PREFIX) && text.endsWith('\`\`\`')) {
132
+ text = text.slice(PAGE_SOURCE_PREFIX.length, -3);
133
+ }
134
+ return text;
135
+ }
136
+
137
+ function appendHighlightedText(container, text, query) {
138
+ const lowerText = text.toLowerCase();
139
+ const lowerQuery = query.toLowerCase();
140
+ let position = 0;
141
+
142
+ while (position < text.length) {
143
+ const matchIndex = lowerText.indexOf(lowerQuery, position);
144
+ if (matchIndex === -1) {
145
+ container.appendChild(document.createTextNode(text.slice(position)));
146
+ return;
147
+ }
148
+ if (matchIndex > position) {
149
+ container.appendChild(document.createTextNode(text.slice(position, matchIndex)));
150
+ }
151
+ const mark = document.createElement('mark');
152
+ mark.textContent = text.slice(matchIndex, matchIndex + query.length);
153
+ container.appendChild(mark);
154
+ position = matchIndex + query.length;
155
+ }
156
+ }
157
+
158
+ function renderSource() {
159
+ const query = searchBox.value.trim();
160
+ xmlContent.textContent = '';
161
+ xmlContent.classList.toggle('empty', source.length === 0);
162
+ if (!source) {
163
+ xmlContent.textContent = 'No page source was returned.';
164
+ } else if (!query) {
165
+ xmlContent.textContent = source;
166
+ } else {
167
+ appendHighlightedText(xmlContent, source, query);
168
+ }
169
+ sourceInfo.textContent = source.length + ' characters';
170
+ }
171
+
172
+ function setSource(nextSource) {
173
+ source = nextSource;
174
+ renderSource();
175
+ }
176
+
177
+ function formatXML() {
178
+ if (!source) return;
179
+ try {
180
+ const parser = new DOMParser();
181
+ const documentNode = parser.parseFromString(source, 'text/xml');
182
+ if (documentNode.querySelector('parsererror')) {
183
+ throw new Error('Invalid XML');
184
+ }
185
+ source = new XMLSerializer().serializeToString(documentNode);
186
+ renderSource();
187
+ } catch {
188
+ window.alert('Failed to format XML');
189
+ }
190
+ }
191
+
192
+ async function copyToClipboard() {
193
+ if (!source) return;
194
+ try {
195
+ await navigator.clipboard.writeText(source);
196
+ } catch {
197
+ window.alert('Failed to copy page source');
198
+ }
199
+ }
200
+
201
+ async function generateLocators() {
202
+ try {
203
+ await sendRequest('tools/call', {name: 'generate_locators', arguments: {}});
204
+ } catch {
205
+ window.alert('Failed to generate locators');
206
+ }
207
+ }
208
+
209
+ window.addEventListener('message', (event) => {
210
+ if (event.source !== window.parent) return;
211
+ const message = event.data;
212
+ if (!message || message.jsonrpc !== '2.0') return;
213
+
214
+ if (message.id !== undefined && pendingRequests.has(message.id)) {
215
+ const pending = pendingRequests.get(message.id);
216
+ pendingRequests.delete(message.id);
217
+ if (message.error) {
218
+ pending.reject(new Error(message.error.message || 'MCP App request failed'));
219
+ } else {
220
+ pending.resolve(message.result);
221
+ }
222
+ return;
223
+ }
224
+
225
+ if (message.method === 'ui/notifications/tool-result') {
226
+ setSource(sourceFromToolResult(message.params));
227
+ }
228
+ });
229
+
230
+ searchBox.addEventListener('input', renderSource);
231
+ document.getElementById('copyButton').addEventListener('click', copyToClipboard);
232
+ document.getElementById('formatButton').addEventListener('click', formatXML);
233
+ document.getElementById('generateButton').addEventListener('click', generateLocators);
234
+
235
+ sendRequest('ui/initialize', {
236
+ protocolVersion: '2026-01-26',
237
+ appInfo: {name: 'Appium Page Source Inspector', version: '1.0.0'},
238
+ appCapabilities: {availableDisplayModes: ['inline', 'fullscreen']},
239
+ })
240
+ .then(() => sendNotification('ui/notifications/initialized', {}))
241
+ .catch(() => {
242
+ sourceInfo.textContent = 'Unable to initialize inspector';
243
+ });
244
+ })();
245
+ </script>
246
+ </body>
247
+ </html>
248
+ `;
249
+ }