appium-mcp 1.82.2 → 1.83.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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## [1.83.0](https://github.com/appium/appium-mcp/compare/v1.82.2...v1.83.0) (2026-06-08)
2
+
3
+ ### Features
4
+
5
+ * emit structured action evidence records ([#386](https://github.com/appium/appium-mcp/issues/386)) ([d42593e](https://github.com/appium/appium-mcp/commit/d42593e86c0bb6535b4e1c3e855c6d7e307e9611))
6
+
1
7
  ## [1.82.2](https://github.com/appium/appium-mcp/compare/v1.82.1...v1.82.2) (2026-06-08)
2
8
 
3
9
  ### Miscellaneous Chores
package/README.md CHANGED
@@ -154,6 +154,7 @@ This will automatically configure the MCP server for use with Claude Code. Make
154
154
  | `AI_VISION_IMAGE_QUALITY` | Optional | JPEG quality 1–100 for compressed screenshots sent to the vision API (default: `80`) |
155
155
  | `SENTENCE_TRANSFORMERS_MODEL` | Optional | Hugging Face model used for semantic search in Appium documentation queries (default: `Xenova/all-MiniLM-L6-v2`) |
156
156
  | `APPIUM_MCP_PERSIST_REMOTE_SESSIONS_PATH` | Optional | Absolute file path to persist attached remote session info across server restarts (JSON format) |
157
+ | `APPIUM_MCP_EVIDENCE` | Optional | Set to `true` or `1` to attach a structured **action evidence record** (locator, resolved element id, context, timing, normalized error code) to `appium_find_element` and `appium_gesture` responses as an `application/vnd.appium.evidence+json` resource block, for CI/debugging. Disabled by default; responses are unchanged when unset. |
157
158
 
158
159
  ### Capabilities
159
160
 
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=evidence.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evidence.test.d.ts","sourceRoot":"","sources":["../../../src/tests/tools/evidence.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,158 @@
1
+ import { describe, test, expect, jest, beforeEach, afterEach, } from '@jest/globals';
2
+ // Mock the session store to avoid pulling in the native drivers it imports.
3
+ jest.unstable_mockModule('../../session-store', () => ({
4
+ getSessionInfo: jest.fn(() => null),
5
+ }));
6
+ const { classifyError, isEvidenceEnabled, withEvidence } = await import('../../tools/evidence.js');
7
+ const ENV_KEY = 'APPIUM_MCP_EVIDENCE';
8
+ function enableEvidence(value = '1') {
9
+ process.env[ENV_KEY] = value;
10
+ }
11
+ const textResult = (text) => ({
12
+ content: [{ type: 'text', text }],
13
+ });
14
+ const errorResult = (text) => ({
15
+ content: [{ type: 'text', text }],
16
+ isError: true,
17
+ });
18
+ function readRecord(result) {
19
+ const block = result.content.find((c) => c.type === 'resource');
20
+ const text = block?.type === 'resource' && 'text' in block.resource
21
+ ? block.resource.text
22
+ : undefined;
23
+ if (typeof text !== 'string') {
24
+ throw new Error('no evidence resource block found');
25
+ }
26
+ return JSON.parse(text);
27
+ }
28
+ describe('classifyError', () => {
29
+ test.each([
30
+ ['An element could not be located on the page', 'ELEMENT_NOT_FOUND'],
31
+ ['no such element: unable to find', 'ELEMENT_NOT_FOUND'],
32
+ ['stale element reference', 'STALE_ELEMENT'],
33
+ ['The operation timed out after 10000ms', 'TIMEOUT'],
34
+ ['no such context WEBVIEW_x', 'CONTEXT_NOT_AVAILABLE'],
35
+ ['invalid selector: //[', 'INVALID_SELECTOR'],
36
+ ['something unexpected exploded', 'ACTION_FAILED'],
37
+ ])('maps %j -> %s', (message, code) => {
38
+ expect(classifyError(new Error(message))).toBe(code);
39
+ });
40
+ test('accepts non-Error values', () => {
41
+ expect(classifyError('plain timeout string')).toBe('TIMEOUT');
42
+ expect(classifyError(undefined)).toBe('ACTION_FAILED');
43
+ });
44
+ });
45
+ describe('isEvidenceEnabled', () => {
46
+ const original = process.env[ENV_KEY];
47
+ afterEach(() => {
48
+ if (original === undefined) {
49
+ delete process.env[ENV_KEY];
50
+ }
51
+ else {
52
+ process.env[ENV_KEY] = original;
53
+ }
54
+ });
55
+ test.each(['1', 'true', 'TRUE'])('enabled for %j', (value) => {
56
+ process.env[ENV_KEY] = value;
57
+ expect(isEvidenceEnabled()).toBe(true);
58
+ });
59
+ test.each(['0', 'false', '', undefined])('disabled for %j', (value) => {
60
+ if (value === undefined) {
61
+ delete process.env[ENV_KEY];
62
+ }
63
+ else {
64
+ process.env[ENV_KEY] = value;
65
+ }
66
+ expect(isEvidenceEnabled()).toBe(false);
67
+ });
68
+ });
69
+ describe('withEvidence', () => {
70
+ const original = process.env[ENV_KEY];
71
+ beforeEach(() => {
72
+ delete process.env[ENV_KEY];
73
+ });
74
+ afterEach(() => {
75
+ if (original === undefined) {
76
+ delete process.env[ENV_KEY];
77
+ }
78
+ else {
79
+ process.env[ENV_KEY] = original;
80
+ }
81
+ });
82
+ test('is a no-op when disabled', () => {
83
+ const result = textResult('ok');
84
+ const out = withEvidence(result, {
85
+ name: 'appium_find_element',
86
+ stage: 'locate',
87
+ startedAt: Date.now(),
88
+ });
89
+ expect(out).toBe(result);
90
+ expect(out.content).toHaveLength(1);
91
+ });
92
+ test('appends a resource block without mutating the text', () => {
93
+ enableEvidence();
94
+ const out = withEvidence(textResult("elementId 'abc'\nfound"), {
95
+ name: 'appium_find_element',
96
+ stage: 'locate',
97
+ startedAt: Date.now(),
98
+ locator: { strategy: 'accessibility id', selector: 'login' },
99
+ element: { webdriverId: 'abc' },
100
+ });
101
+ expect(out.content[0]).toEqual({
102
+ type: 'text',
103
+ text: "elementId 'abc'\nfound",
104
+ });
105
+ const block = out.content[1];
106
+ expect(block.type).toBe('resource');
107
+ if (block.type === 'resource') {
108
+ expect(block.resource.uri).toMatch(/^evidence:\/\//);
109
+ expect(block.resource.mimeType).toBe('application/vnd.appium.evidence+json');
110
+ }
111
+ });
112
+ test('builds a success record', () => {
113
+ enableEvidence();
114
+ const startedAt = Date.now();
115
+ const record = readRecord(withEvidence(textResult('found'), {
116
+ name: 'appium_find_element',
117
+ stage: 'locate',
118
+ startedAt,
119
+ locator: { strategy: 'id', selector: 'btn' },
120
+ element: { webdriverId: 'el-1' },
121
+ }));
122
+ expect(record.schemaVersion).toBe(1);
123
+ expect(record.producer.name).toBe('appium-mcp');
124
+ expect(record.evidenceId).toEqual(expect.any(String));
125
+ expect(record.status).toBe('success');
126
+ expect(record.action.name).toBe('appium_find_element');
127
+ expect(record.action.stage).toBe('locate');
128
+ expect(record.action.locator).toEqual({ strategy: 'id', selector: 'btn' });
129
+ expect(record.action.element).toEqual({ webdriverId: 'el-1' });
130
+ expect(record.error).toBeUndefined();
131
+ expect(record.timing.durationMs).toBeGreaterThanOrEqual(0);
132
+ expect(record.timing.startedAt).toBe(new Date(startedAt).toISOString());
133
+ });
134
+ test('classifies error from the passed error object', () => {
135
+ enableEvidence();
136
+ const record = readRecord(withEvidence(errorResult('Failed to find element. Error: stale'), {
137
+ name: 'appium_find_element',
138
+ stage: 'locate',
139
+ startedAt: Date.now(),
140
+ error: new Error('stale element reference'),
141
+ }));
142
+ expect(record.status).toBe('error');
143
+ expect(record.error?.code).toBe('STALE_ELEMENT');
144
+ expect(record.error?.message).toBe('stale element reference');
145
+ });
146
+ test('falls back to result text when no error object is supplied', () => {
147
+ enableEvidence();
148
+ const record = readRecord(withEvidence(errorResult('The operation timed out'), {
149
+ name: 'appium_gesture',
150
+ stage: 'interact',
151
+ startedAt: Date.now(),
152
+ }));
153
+ expect(record.status).toBe('error');
154
+ expect(record.error?.code).toBe('TIMEOUT');
155
+ expect(record.error?.message).toBe('The operation timed out');
156
+ });
157
+ });
158
+ //# sourceMappingURL=evidence.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evidence.test.js","sourceRoot":"","sources":["../../../src/tests/tools/evidence.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,82 @@
1
+ import type { ContentResult } from 'fastmcp';
2
+ /**
3
+ * Structured, machine-readable trace of a single tool action, so CI and agents
4
+ * can diagnose failures without parsing prose.
5
+ *
6
+ * This is an *action* record. A future run-level bundle may reference many of
7
+ * these plus heavier artifacts (screenshots, page source).
8
+ */
9
+ export interface ActionEvidenceRecord {
10
+ schemaVersion: 1;
11
+ producer: {
12
+ name: 'appium-mcp';
13
+ version: string;
14
+ };
15
+ evidenceId: string;
16
+ status: 'success' | 'error';
17
+ /**
18
+ * Action-specific details. `stage`/`locator`/`element` are optional because
19
+ * they only apply to tools that resolve or interact with elements (find,
20
+ * gesture); tools like screenshot carry just `name`.
21
+ */
22
+ action: {
23
+ name: string;
24
+ stage?: EvidenceStage;
25
+ locator?: {
26
+ strategy: string;
27
+ selector: string;
28
+ };
29
+ element?: {
30
+ webdriverId: string;
31
+ };
32
+ };
33
+ context?: {
34
+ platform?: string;
35
+ contextName?: string;
36
+ };
37
+ timing: {
38
+ startedAt: string;
39
+ finishedAt: string;
40
+ durationMs: number;
41
+ };
42
+ error?: {
43
+ code: EvidenceErrorCode;
44
+ message: string;
45
+ };
46
+ }
47
+ export type EvidenceStage = 'locate' | 'interact' | 'capture';
48
+ export type EvidenceErrorCode = 'ELEMENT_NOT_FOUND' | 'TIMEOUT' | 'STALE_ELEMENT' | 'CONTEXT_NOT_AVAILABLE' | 'INVALID_SELECTOR' | 'ACTION_FAILED';
49
+ /** Inputs a handler supplies to describe the action it performed. */
50
+ export interface EvidenceInput {
51
+ name: string;
52
+ startedAt: number;
53
+ stage?: EvidenceStage;
54
+ locator?: ActionEvidenceRecord['action']['locator'];
55
+ element?: ActionEvidenceRecord['action']['element'];
56
+ context?: ActionEvidenceRecord['context'];
57
+ error?: unknown;
58
+ }
59
+ /**
60
+ * When disabled, evidence is never built or attached and tool
61
+ * responses are byte-for-byte unchanged.
62
+ */
63
+ export declare function isEvidenceEnabled(): boolean;
64
+ /**
65
+ * Attach an evidence record to an existing tool result as a `resource` content
66
+ * block, leaving the original text untouched. No-op when evidence is disabled.
67
+ */
68
+ export declare function withEvidence(result: ContentResult, input: EvidenceInput): ContentResult;
69
+ /**
70
+ * Snapshot the active session's platform and current context for the evidence
71
+ * record. Returns undefined when there is no session to read, so the field is
72
+ * simply omitted. The session-store import is deferred until evidence is
73
+ * enabled so the disabled path pulls in no driver dependencies.
74
+ */
75
+ export declare function evidenceContext(sessionId?: string): Promise<ActionEvidenceRecord['context'] | undefined>;
76
+ /**
77
+ * Map a raw Appium/WebDriver error to a stable, normalized code. The codes are
78
+ * also the foundation for failure-reason hypotheses. Inspects the error name
79
+ * first, then the message, falling back to ACTION_FAILED.
80
+ */
81
+ export declare function classifyError(err: unknown): EvidenceErrorCode;
82
+ //# sourceMappingURL=evidence.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evidence.d.ts","sourceRoot":"","sources":["../../src/tools/evidence.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAG7C;;;;;;GAMG;AACH,MAAM,WAAW,oBAAoB;IACnC,aAAa,EAAE,CAAC,CAAC;IACjB,QAAQ,EAAE;QAAE,IAAI,EAAE,YAAY,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAClD,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC;IAC5B;;;;OAIG;IACH,MAAM,EAAE;QACN,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,CAAC,EAAE,aAAa,CAAC;QACtB,OAAO,CAAC,EAAE;YAAE,QAAQ,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAA;SAAE,CAAC;QACjD,OAAO,CAAC,EAAE;YAAE,WAAW,EAAE,MAAM,CAAA;SAAE,CAAC;KACnC,CAAC;IACF,OAAO,CAAC,EAAE;QACR,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,CAAC;IACF,MAAM,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;IACtE,KAAK,CAAC,EAAE;QAAE,IAAI,EAAE,iBAAiB,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CACtD;AAED,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,UAAU,GAAG,SAAS,CAAC;AAE9D,MAAM,MAAM,iBAAiB,GACzB,mBAAmB,GACnB,SAAS,GACT,eAAe,GACf,uBAAuB,GACvB,kBAAkB,GAClB,eAAe,CAAC;AAIpB,qEAAqE;AACrE,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,OAAO,CAAC,EAAE,oBAAoB,CAAC,QAAQ,CAAC,CAAC,SAAS,CAAC,CAAC;IACpD,OAAO,CAAC,EAAE,oBAAoB,CAAC,QAAQ,CAAC,CAAC,SAAS,CAAC,CAAC;IACpD,OAAO,CAAC,EAAE,oBAAoB,CAAC,SAAS,CAAC,CAAC;IAC1C,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAG3C;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,aAAa,EACrB,KAAK,EAAE,aAAa,GACnB,aAAa,CAmBf;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CACnC,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,oBAAoB,CAAC,SAAS,CAAC,GAAG,SAAS,CAAC,CAatD;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG,iBAAiB,CAqB7D"}
@@ -0,0 +1,124 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import pkg from '../../package.json' with { type: 'json' };
3
+ const EVIDENCE_MIME_TYPE = 'application/vnd.appium.evidence+json';
4
+ /**
5
+ * When disabled, evidence is never built or attached and tool
6
+ * responses are byte-for-byte unchanged.
7
+ */
8
+ export function isEvidenceEnabled() {
9
+ const raw = process.env.APPIUM_MCP_EVIDENCE?.trim().toLowerCase();
10
+ return raw === '1' || raw === 'true';
11
+ }
12
+ /**
13
+ * Attach an evidence record to an existing tool result as a `resource` content
14
+ * block, leaving the original text untouched. No-op when evidence is disabled.
15
+ */
16
+ export function withEvidence(result, input) {
17
+ if (!isEvidenceEnabled()) {
18
+ return result;
19
+ }
20
+ const record = buildRecord(result, input);
21
+ return {
22
+ ...result,
23
+ content: [
24
+ ...result.content,
25
+ {
26
+ type: 'resource',
27
+ resource: {
28
+ uri: `evidence://${record.evidenceId}`,
29
+ mimeType: EVIDENCE_MIME_TYPE,
30
+ text: JSON.stringify(record),
31
+ },
32
+ },
33
+ ],
34
+ };
35
+ }
36
+ /**
37
+ * Snapshot the active session's platform and current context for the evidence
38
+ * record. Returns undefined when there is no session to read, so the field is
39
+ * simply omitted. The session-store import is deferred until evidence is
40
+ * enabled so the disabled path pulls in no driver dependencies.
41
+ */
42
+ export async function evidenceContext(sessionId) {
43
+ if (!isEvidenceEnabled()) {
44
+ return undefined;
45
+ }
46
+ const { getSessionInfo } = await import('../session-store.js');
47
+ const info = getSessionInfo(sessionId);
48
+ if (!info) {
49
+ return undefined;
50
+ }
51
+ return {
52
+ ...(info.metadata.platform ? { platform: info.metadata.platform } : {}),
53
+ ...(info.currentContext ? { contextName: info.currentContext } : {}),
54
+ };
55
+ }
56
+ /**
57
+ * Map a raw Appium/WebDriver error to a stable, normalized code. The codes are
58
+ * also the foundation for failure-reason hypotheses. Inspects the error name
59
+ * first, then the message, falling back to ACTION_FAILED.
60
+ */
61
+ export function classifyError(err) {
62
+ const name = err instanceof Error ? err.name : '';
63
+ const message = (err instanceof Error ? err.message : String(err)) ?? '';
64
+ const haystack = `${name} ${message}`.toLowerCase();
65
+ if (/no such element|could not be located|element not found/.test(haystack)) {
66
+ return 'ELEMENT_NOT_FOUND';
67
+ }
68
+ if (/stale element/.test(haystack)) {
69
+ return 'STALE_ELEMENT';
70
+ }
71
+ if (/timed? ?out|timeout/.test(haystack)) {
72
+ return 'TIMEOUT';
73
+ }
74
+ if (/no such context|context.*not.*(found|available)/.test(haystack)) {
75
+ return 'CONTEXT_NOT_AVAILABLE';
76
+ }
77
+ if (/invalid selector|invalid.*(locator|xpath)/.test(haystack)) {
78
+ return 'INVALID_SELECTOR';
79
+ }
80
+ return 'ACTION_FAILED';
81
+ }
82
+ function buildRecord(result, input) {
83
+ const finished = Date.now();
84
+ const status = result.isError || input.error !== undefined ? 'error' : 'success';
85
+ return {
86
+ schemaVersion: 1,
87
+ producer: { name: 'appium-mcp', version: pkg.version },
88
+ evidenceId: randomUUID(),
89
+ status,
90
+ action: {
91
+ name: input.name,
92
+ ...(input.stage ? { stage: input.stage } : {}),
93
+ ...(input.locator ? { locator: input.locator } : {}),
94
+ ...(input.element ? { element: input.element } : {}),
95
+ },
96
+ ...(input.context ? { context: input.context } : {}),
97
+ timing: {
98
+ startedAt: new Date(input.startedAt).toISOString(),
99
+ finishedAt: new Date(finished).toISOString(),
100
+ durationMs: finished - input.startedAt,
101
+ },
102
+ ...(status === 'error' ? { error: buildError(input.error, result) } : {}),
103
+ };
104
+ }
105
+ /**
106
+ * Resolve the error message, then classify from the same source so the code
107
+ * and message always agree — handlers that swallow errors only expose the
108
+ * result text, so fall back to that when no error object is passed.
109
+ */
110
+ function buildError(err, result) {
111
+ const message = errorMessage(err, result);
112
+ return { code: classifyError(err ?? message), message };
113
+ }
114
+ function errorMessage(err, result) {
115
+ if (err instanceof Error) {
116
+ return err.message;
117
+ }
118
+ if (err !== undefined) {
119
+ return String(err);
120
+ }
121
+ const text = result.content.find((c) => c.type === 'text');
122
+ return text && 'text' in text ? text.text : 'Unknown error';
123
+ }
124
+ //# sourceMappingURL=evidence.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evidence.js","sourceRoot":"","sources":["../../src/tools/evidence.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"gesture.d.ts","sourceRoot":"","sources":["../../../src/tools/gestures/gesture.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,OAAO,EAAE,MAAM,SAAS,CAAC;AActD,MAAM,CAAC,OAAO,UAAU,OAAO,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAkDrD"}
1
+ {"version":3,"file":"gesture.d.ts","sourceRoot":"","sources":["../../../src/tools/gestures/gesture.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,OAAO,EAAE,MAAM,SAAS,CAAC;AA0BtD,MAAM,CAAC,OAAO,UAAU,OAAO,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAwCrD"}
@@ -1,10 +1,12 @@
1
1
  import { errorResult, resolveDriver, textResult, toolErrorMessage, } from '../tool-response.js';
2
- import { GESTURE_ACTIONS, gestureSchema } from './schema.js';
2
+ import { GESTURE_ACTIONS, gestureSchema, } from './schema.js';
3
3
  import { handleTap, handleDoubleTap, handleLongPress } from './handlers/tap.js';
4
4
  import { handleScroll, handleSwipe } from './handlers/swipe-scroll.js';
5
5
  import { handlePinchZoom } from './handlers/pinch.js';
6
6
  import { handleScrollToElement } from './handlers/scroll-to-element.js';
7
7
  import { back } from '../../command.js';
8
+ import { withEvidence, evidenceContext, } from '../evidence.js';
9
+ import { isAiElementUUID } from './handlers/ai-element.js';
8
10
  export default function gesture(server) {
9
11
  server.addTool({
10
12
  name: 'appium_gesture',
@@ -23,31 +25,51 @@ export default function gesture(server) {
23
25
  return resolved.result;
24
26
  }
25
27
  const { driver } = resolved;
26
- switch (args.action) {
27
- case 'back':
28
- try {
29
- await back(driver);
30
- return textResult('Successfully performed back action.');
31
- }
32
- catch (err) {
33
- return errorResult(`Failed to perform back action. ${toolErrorMessage(err)}`);
34
- }
35
- case 'tap':
36
- return handleTap(driver, args);
37
- case 'double_tap':
38
- return handleDoubleTap(driver, args);
39
- case 'long_press':
40
- return handleLongPress(driver, args);
41
- case 'scroll':
42
- return handleScroll(driver, args);
43
- case 'swipe':
44
- return handleSwipe(driver, args);
45
- case 'pinch_zoom':
46
- return handlePinchZoom(driver, args);
47
- case 'scroll_to_element':
48
- return handleScrollToElement(driver, args);
49
- }
28
+ const startedAt = Date.now();
29
+ const context = await evidenceContext(args.sessionId);
30
+ const result = await dispatchGesture(driver, args);
31
+ return withEvidence(result, {
32
+ name: 'appium_gesture',
33
+ stage: gestureStage(args.action),
34
+ startedAt,
35
+ context,
36
+ ...(args.strategy && args.selector
37
+ ? { locator: { strategy: args.strategy, selector: args.selector } }
38
+ : {}),
39
+ ...(args.elementUUID && !isAiElementUUID(args.elementUUID)
40
+ ? { element: { webdriverId: args.elementUUID } }
41
+ : {}),
42
+ });
50
43
  },
51
44
  });
52
45
  }
46
+ async function dispatchGesture(driver, args) {
47
+ switch (args.action) {
48
+ case 'back':
49
+ try {
50
+ await back(driver);
51
+ return textResult('Successfully performed back action.');
52
+ }
53
+ catch (err) {
54
+ return errorResult(`Failed to perform back action. ${toolErrorMessage(err)}`);
55
+ }
56
+ case 'tap':
57
+ return handleTap(driver, args);
58
+ case 'double_tap':
59
+ return handleDoubleTap(driver, args);
60
+ case 'long_press':
61
+ return handleLongPress(driver, args);
62
+ case 'scroll':
63
+ return handleScroll(driver, args);
64
+ case 'swipe':
65
+ return handleSwipe(driver, args);
66
+ case 'pinch_zoom':
67
+ return handlePinchZoom(driver, args);
68
+ case 'scroll_to_element':
69
+ return handleScrollToElement(driver, args);
70
+ }
71
+ }
72
+ function gestureStage(action) {
73
+ return action === 'scroll_to_element' ? 'locate' : 'interact';
74
+ }
53
75
  //# sourceMappingURL=gesture.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"gesture.js","sourceRoot":"","sources":["../../../src/tools/gestures/gesture.ts"],"names":[],"mappings":"AACA,OAAO,EACL,WAAW,EACX,aAAa,EACb,UAAU,EACV,gBAAgB,GACjB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,eAAe,EAAE,aAAa,EAAoB,MAAM,aAAa,CAAC;AAC/E,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAChF,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACvE,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AACxE,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAExC,MAAM,CAAC,OAAO,UAAU,OAAO,CAAC,MAAe;IAC7C,MAAM,CAAC,OAAO,CAAC;QACb,IAAI,EAAE,gBAAgB;QACtB,WAAW,EACT,oDAAoD,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;YAClF,gFAAgF;YAChF,yFAAyF;YACzF,gGAAgG;QAClG,UAAU,EAAE,aAAa;QACzB,WAAW,EAAE;YACX,YAAY,EAAE,KAAK;YACnB,aAAa,EAAE,KAAK;SACrB;QACD,OAAO,EAAE,KAAK,EACZ,IAAiB,EACjB,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,EAAE,MAAM,EAAE,GAAG,QAAQ,CAAC;YAE5B,QAAQ,IAAI,CAAC,MAAM,EAAE,CAAC;gBACpB,KAAK,MAAM;oBACT,IAAI,CAAC;wBACH,MAAM,IAAI,CAAC,MAAM,CAAC,CAAC;wBACnB,OAAO,UAAU,CAAC,qCAAqC,CAAC,CAAC;oBAC3D,CAAC;oBAAC,OAAO,GAAY,EAAE,CAAC;wBACtB,OAAO,WAAW,CAChB,kCAAkC,gBAAgB,CAAC,GAAG,CAAC,EAAE,CAC1D,CAAC;oBACJ,CAAC;gBACH,KAAK,KAAK;oBACR,OAAO,SAAS,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;gBACjC,KAAK,YAAY;oBACf,OAAO,eAAe,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;gBACvC,KAAK,YAAY;oBACf,OAAO,eAAe,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;gBACvC,KAAK,QAAQ;oBACX,OAAO,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;gBACpC,KAAK,OAAO;oBACV,OAAO,WAAW,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;gBACnC,KAAK,YAAY;oBACf,OAAO,eAAe,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;gBACvC,KAAK,mBAAmB;oBACtB,OAAO,qBAAqB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;YAC/C,CAAC;QACH,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"gesture.js","sourceRoot":"","sources":["../../../src/tools/gestures/gesture.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"find.d.ts","sourceRoot":"","sources":["../../../src/tools/interactions/find.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,OAAO,EAAE,MAAM,SAAS,CAAC;AACtD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AASxB,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;iBAqC5B,CAAC;AAEH,MAAM,CAAC,OAAO,UAAU,WAAW,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CA2CzD"}
1
+ {"version":3,"file":"find.d.ts","sourceRoot":"","sources":["../../../src/tools/interactions/find.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,OAAO,EAAE,MAAM,SAAS,CAAC;AACtD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAUxB,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;iBAqC5B,CAAC;AAEH,MAAM,CAAC,OAAO,UAAU,WAAW,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CA4EzD"}
@@ -1,5 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import { resolveDriver, textResultWithPrimaryElementId, errorResult, toolErrorMessage, readWebElementId, } from '../tool-response.js';
3
+ import { withEvidence, evidenceContext } from '../evidence.js';
3
4
  export const findElementSchema = z.object({
4
5
  strategy: z
5
6
  .enum([
@@ -57,17 +58,39 @@ export default function findElement(server) {
57
58
  return resolved.result;
58
59
  }
59
60
  const { driver } = resolved;
61
+ const startedAt = Date.now();
62
+ const locator = { strategy: args.strategy, selector: args.selector };
63
+ const context = await evidenceContext(args.sessionId);
60
64
  try {
61
65
  const element = await driver.findElement(args.strategy, args.selector);
62
66
  const elementId = readWebElementId(element);
63
67
  if (!elementId) {
64
- return errorResult('Element was returned without a valid element ID');
68
+ return withEvidence(errorResult('Element was returned without a valid element ID'), {
69
+ name: 'appium_find_element',
70
+ stage: 'locate',
71
+ startedAt,
72
+ locator,
73
+ context,
74
+ });
65
75
  }
66
- return textResultWithPrimaryElementId(elementId, `Successfully found element ${args.selector} with strategy ${args.strategy}.`);
76
+ return withEvidence(textResultWithPrimaryElementId(elementId, `Successfully found element ${args.selector} with strategy ${args.strategy}.`), {
77
+ name: 'appium_find_element',
78
+ stage: 'locate',
79
+ startedAt,
80
+ locator,
81
+ element: { webdriverId: elementId },
82
+ context,
83
+ });
67
84
  }
68
85
  catch (err) {
69
- const errorMessage = toolErrorMessage(err);
70
- return errorResult(`Failed to find element. Error: ${errorMessage}`);
86
+ return withEvidence(errorResult(`Failed to find element. Error: ${toolErrorMessage(err)}`), {
87
+ name: 'appium_find_element',
88
+ stage: 'locate',
89
+ startedAt,
90
+ locator,
91
+ context,
92
+ error: err,
93
+ });
71
94
  }
72
95
  },
73
96
  });
@@ -1 +1 @@
1
- {"version":3,"file":"find.js","sourceRoot":"","sources":["../../../src/tools/interactions/find.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EACL,aAAa,EACb,8BAA8B,EAC9B,WAAW,EACX,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,qBAAqB,CAAC;AAE7B,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC;IACxC,QAAQ,EAAE,CAAC;SACR,IAAI,CAAC;QACJ,kBAAkB;QAClB,IAAI;QACJ,uBAAuB;QACvB,kBAAkB;QAClB,sBAAsB;QACtB,OAAO;QACP,MAAM;QACN,YAAY;QACZ,cAAc;KACf,CAAC;SACD,QAAQ,CACP,2CAA2C;QACzC,+DAA+D;QAC/D,8DAA8D;QAC9D,gDAAgD;QAChD,wDAAwD;QACxD,oEAAoE;QACpE,6EAA6E;QAC7E,2CAA2C;QAC3C,qDAAqD;QACrD,mDAAmD;QACnD,yFAAyF;QACzF,uGAAuG,CAC1G;IACH,QAAQ,EAAE,CAAC;SACR,MAAM,EAAE;SACR,QAAQ,CACP,2CAA2C;QACzC,6GAA6G,CAChH;IACH,SAAS,EAAE,CAAC;SACT,MAAM,EAAE;SACR,QAAQ,EAAE;SACV,QAAQ,CAAC,4DAA4D,CAAC;CAC1E,CAAC,CAAC;AAEH,MAAM,CAAC,OAAO,UAAU,WAAW,CAAC,MAAe;IACjD,MAAM,CAAC,OAAO,CAAC;QACb,IAAI,EAAE,qBAAqB;QAC3B,WAAW,EAAE;;;;;;;;mGAQkF;QAC/F,UAAU,EAAE,iBAAiB;QAC7B,WAAW,EAAE;YACX,YAAY,EAAE,IAAI;YAClB,aAAa,EAAE,KAAK;SACrB;QACD,OAAO,EAAE,KAAK,EACZ,IAAuC,EACvC,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,EAAE,MAAM,EAAE,GAAG,QAAQ,CAAC;YAE5B,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;gBACvE,MAAM,SAAS,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;gBAC5C,IAAI,CAAC,SAAS,EAAE,CAAC;oBACf,OAAO,WAAW,CAAC,iDAAiD,CAAC,CAAC;gBACxE,CAAC;gBACD,OAAO,8BAA8B,CACnC,SAAS,EACT,8BAA8B,IAAI,CAAC,QAAQ,kBAAkB,IAAI,CAAC,QAAQ,GAAG,CAC9E,CAAC;YACJ,CAAC;YAAC,OAAO,GAAY,EAAE,CAAC;gBACtB,MAAM,YAAY,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC;gBAC3C,OAAO,WAAW,CAAC,kCAAkC,YAAY,EAAE,CAAC,CAAC;YACvE,CAAC;QACH,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"find.js","sourceRoot":"","sources":["../../../src/tools/interactions/find.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EACL,aAAa,EACb,8BAA8B,EAC9B,WAAW,EACX,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAE/D,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC;IACxC,QAAQ,EAAE,CAAC;SACR,IAAI,CAAC;QACJ,kBAAkB;QAClB,IAAI;QACJ,uBAAuB;QACvB,kBAAkB;QAClB,sBAAsB;QACtB,OAAO;QACP,MAAM;QACN,YAAY;QACZ,cAAc;KACf,CAAC;SACD,QAAQ,CACP,2CAA2C;QACzC,+DAA+D;QAC/D,8DAA8D;QAC9D,gDAAgD;QAChD,wDAAwD;QACxD,oEAAoE;QACpE,6EAA6E;QAC7E,2CAA2C;QAC3C,qDAAqD;QACrD,mDAAmD;QACnD,yFAAyF;QACzF,uGAAuG,CAC1G;IACH,QAAQ,EAAE,CAAC;SACR,MAAM,EAAE;SACR,QAAQ,CACP,2CAA2C;QACzC,6GAA6G,CAChH;IACH,SAAS,EAAE,CAAC;SACT,MAAM,EAAE;SACR,QAAQ,EAAE;SACV,QAAQ,CAAC,4DAA4D,CAAC;CAC1E,CAAC,CAAC;AAEH,MAAM,CAAC,OAAO,UAAU,WAAW,CAAC,MAAe;IACjD,MAAM,CAAC,OAAO,CAAC;QACb,IAAI,EAAE,qBAAqB;QAC3B,WAAW,EAAE;;;;;;;;mGAQkF;QAC/F,UAAU,EAAE,iBAAiB;QAC7B,WAAW,EAAE;YACX,YAAY,EAAE,IAAI;YAClB,aAAa,EAAE,KAAK;SACrB;QACD,OAAO,EAAE,KAAK,EACZ,IAAuC,EACvC,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,EAAE,MAAM,EAAE,GAAG,QAAQ,CAAC;YAE5B,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAC7B,MAAM,OAAO,GAAG,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC;YACrE,MAAM,OAAO,GAAG,MAAM,eAAe,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YACtD,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;gBACvE,MAAM,SAAS,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;gBAC5C,IAAI,CAAC,SAAS,EAAE,CAAC;oBACf,OAAO,YAAY,CACjB,WAAW,CAAC,iDAAiD,CAAC,EAC9D;wBACE,IAAI,EAAE,qBAAqB;wBAC3B,KAAK,EAAE,QAAQ;wBACf,SAAS;wBACT,OAAO;wBACP,OAAO;qBACR,CACF,CAAC;gBACJ,CAAC;gBACD,OAAO,YAAY,CACjB,8BAA8B,CAC5B,SAAS,EACT,8BAA8B,IAAI,CAAC,QAAQ,kBAAkB,IAAI,CAAC,QAAQ,GAAG,CAC9E,EACD;oBACE,IAAI,EAAE,qBAAqB;oBAC3B,KAAK,EAAE,QAAQ;oBACf,SAAS;oBACT,OAAO;oBACP,OAAO,EAAE,EAAE,WAAW,EAAE,SAAS,EAAE;oBACnC,OAAO;iBACR,CACF,CAAC;YACJ,CAAC;YAAC,OAAO,GAAY,EAAE,CAAC;gBACtB,OAAO,YAAY,CACjB,WAAW,CACT,kCAAkC,gBAAgB,CAAC,GAAG,CAAC,EAAE,CAC1D,EACD;oBACE,IAAI,EAAE,qBAAqB;oBAC3B,KAAK,EAAE,QAAQ;oBACf,SAAS;oBACT,OAAO;oBACP,OAAO;oBACP,KAAK,EAAE,GAAG;iBACX,CACF,CAAC;YACJ,CAAC;QACH,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "appium-mcp",
3
3
  "mcpName": "io.github.appium/appium-mcp",
4
- "version": "1.82.2",
4
+ "version": "1.83.0",
5
5
  "type": "module",
6
6
  "repository": {
7
7
  "type": "git",
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.82.2",
6
+ "version": "1.83.0",
7
7
  "packages": [
8
8
  {
9
9
  "registryType": "npm",
10
10
  "identifier": "appium-mcp",
11
- "version": "1.82.2",
11
+ "version": "1.83.0",
12
12
  "transport": {
13
13
  "type": "stdio"
14
14
  }
Binary file
@@ -0,0 +1,198 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import type { ContentResult } from 'fastmcp';
3
+ import pkg from '../../package.json' with { type: 'json' };
4
+
5
+ /**
6
+ * Structured, machine-readable trace of a single tool action, so CI and agents
7
+ * can diagnose failures without parsing prose.
8
+ *
9
+ * This is an *action* record. A future run-level bundle may reference many of
10
+ * these plus heavier artifacts (screenshots, page source).
11
+ */
12
+ export interface ActionEvidenceRecord {
13
+ schemaVersion: 1;
14
+ producer: { name: 'appium-mcp'; version: string };
15
+ evidenceId: string;
16
+ status: 'success' | 'error';
17
+ /**
18
+ * Action-specific details. `stage`/`locator`/`element` are optional because
19
+ * they only apply to tools that resolve or interact with elements (find,
20
+ * gesture); tools like screenshot carry just `name`.
21
+ */
22
+ action: {
23
+ name: string;
24
+ stage?: EvidenceStage;
25
+ locator?: { strategy: string; selector: string };
26
+ element?: { webdriverId: string };
27
+ };
28
+ context?: {
29
+ platform?: string;
30
+ contextName?: string;
31
+ };
32
+ timing: { startedAt: string; finishedAt: string; durationMs: number };
33
+ error?: { code: EvidenceErrorCode; message: string };
34
+ }
35
+
36
+ export type EvidenceStage = 'locate' | 'interact' | 'capture';
37
+
38
+ export type EvidenceErrorCode =
39
+ | 'ELEMENT_NOT_FOUND'
40
+ | 'TIMEOUT'
41
+ | 'STALE_ELEMENT'
42
+ | 'CONTEXT_NOT_AVAILABLE'
43
+ | 'INVALID_SELECTOR'
44
+ | 'ACTION_FAILED';
45
+
46
+ const EVIDENCE_MIME_TYPE = 'application/vnd.appium.evidence+json';
47
+
48
+ /** Inputs a handler supplies to describe the action it performed. */
49
+ export interface EvidenceInput {
50
+ name: string;
51
+ startedAt: number;
52
+ stage?: EvidenceStage;
53
+ locator?: ActionEvidenceRecord['action']['locator'];
54
+ element?: ActionEvidenceRecord['action']['element'];
55
+ context?: ActionEvidenceRecord['context'];
56
+ error?: unknown;
57
+ }
58
+
59
+ /**
60
+ * When disabled, evidence is never built or attached and tool
61
+ * responses are byte-for-byte unchanged.
62
+ */
63
+ export function isEvidenceEnabled(): boolean {
64
+ const raw = process.env.APPIUM_MCP_EVIDENCE?.trim().toLowerCase();
65
+ return raw === '1' || raw === 'true';
66
+ }
67
+
68
+ /**
69
+ * Attach an evidence record to an existing tool result as a `resource` content
70
+ * block, leaving the original text untouched. No-op when evidence is disabled.
71
+ */
72
+ export function withEvidence(
73
+ result: ContentResult,
74
+ input: EvidenceInput
75
+ ): ContentResult {
76
+ if (!isEvidenceEnabled()) {
77
+ return result;
78
+ }
79
+ const record = buildRecord(result, input);
80
+ return {
81
+ ...result,
82
+ content: [
83
+ ...result.content,
84
+ {
85
+ type: 'resource',
86
+ resource: {
87
+ uri: `evidence://${record.evidenceId}`,
88
+ mimeType: EVIDENCE_MIME_TYPE,
89
+ text: JSON.stringify(record),
90
+ },
91
+ },
92
+ ],
93
+ };
94
+ }
95
+
96
+ /**
97
+ * Snapshot the active session's platform and current context for the evidence
98
+ * record. Returns undefined when there is no session to read, so the field is
99
+ * simply omitted. The session-store import is deferred until evidence is
100
+ * enabled so the disabled path pulls in no driver dependencies.
101
+ */
102
+ export async function evidenceContext(
103
+ sessionId?: string
104
+ ): Promise<ActionEvidenceRecord['context'] | undefined> {
105
+ if (!isEvidenceEnabled()) {
106
+ return undefined;
107
+ }
108
+ const { getSessionInfo } = await import('../session-store.js');
109
+ const info = getSessionInfo(sessionId);
110
+ if (!info) {
111
+ return undefined;
112
+ }
113
+ return {
114
+ ...(info.metadata.platform ? { platform: info.metadata.platform } : {}),
115
+ ...(info.currentContext ? { contextName: info.currentContext } : {}),
116
+ };
117
+ }
118
+
119
+ /**
120
+ * Map a raw Appium/WebDriver error to a stable, normalized code. The codes are
121
+ * also the foundation for failure-reason hypotheses. Inspects the error name
122
+ * first, then the message, falling back to ACTION_FAILED.
123
+ */
124
+ export function classifyError(err: unknown): EvidenceErrorCode {
125
+ const name = err instanceof Error ? err.name : '';
126
+ const message = (err instanceof Error ? err.message : String(err)) ?? '';
127
+ const haystack = `${name} ${message}`.toLowerCase();
128
+
129
+ if (/no such element|could not be located|element not found/.test(haystack)) {
130
+ return 'ELEMENT_NOT_FOUND';
131
+ }
132
+ if (/stale element/.test(haystack)) {
133
+ return 'STALE_ELEMENT';
134
+ }
135
+ if (/timed? ?out|timeout/.test(haystack)) {
136
+ return 'TIMEOUT';
137
+ }
138
+ if (/no such context|context.*not.*(found|available)/.test(haystack)) {
139
+ return 'CONTEXT_NOT_AVAILABLE';
140
+ }
141
+ if (/invalid selector|invalid.*(locator|xpath)/.test(haystack)) {
142
+ return 'INVALID_SELECTOR';
143
+ }
144
+ return 'ACTION_FAILED';
145
+ }
146
+
147
+ function buildRecord(
148
+ result: ContentResult,
149
+ input: EvidenceInput
150
+ ): ActionEvidenceRecord {
151
+ const finished = Date.now();
152
+ const status: ActionEvidenceRecord['status'] =
153
+ result.isError || input.error !== undefined ? 'error' : 'success';
154
+
155
+ return {
156
+ schemaVersion: 1,
157
+ producer: { name: 'appium-mcp', version: pkg.version },
158
+ evidenceId: randomUUID(),
159
+ status,
160
+ action: {
161
+ name: input.name,
162
+ ...(input.stage ? { stage: input.stage } : {}),
163
+ ...(input.locator ? { locator: input.locator } : {}),
164
+ ...(input.element ? { element: input.element } : {}),
165
+ },
166
+ ...(input.context ? { context: input.context } : {}),
167
+ timing: {
168
+ startedAt: new Date(input.startedAt).toISOString(),
169
+ finishedAt: new Date(finished).toISOString(),
170
+ durationMs: finished - input.startedAt,
171
+ },
172
+ ...(status === 'error' ? { error: buildError(input.error, result) } : {}),
173
+ };
174
+ }
175
+
176
+ /**
177
+ * Resolve the error message, then classify from the same source so the code
178
+ * and message always agree — handlers that swallow errors only expose the
179
+ * result text, so fall back to that when no error object is passed.
180
+ */
181
+ function buildError(
182
+ err: unknown,
183
+ result: ContentResult
184
+ ): NonNullable<ActionEvidenceRecord['error']> {
185
+ const message = errorMessage(err, result);
186
+ return { code: classifyError(err ?? message), message };
187
+ }
188
+
189
+ function errorMessage(err: unknown, result: ContentResult): string {
190
+ if (err instanceof Error) {
191
+ return err.message;
192
+ }
193
+ if (err !== undefined) {
194
+ return String(err);
195
+ }
196
+ const text = result.content.find((c) => c.type === 'text');
197
+ return text && 'text' in text ? text.text : 'Unknown error';
198
+ }
@@ -1,16 +1,28 @@
1
1
  import type { ContentResult, FastMCP } from 'fastmcp';
2
+ import type { DriverInstance } from '../../session-store.js';
2
3
  import {
3
4
  errorResult,
4
5
  resolveDriver,
5
6
  textResult,
6
7
  toolErrorMessage,
7
8
  } from '../tool-response.js';
8
- import { GESTURE_ACTIONS, gestureSchema, type GestureArgs } from './schema.js';
9
+ import {
10
+ GESTURE_ACTIONS,
11
+ gestureSchema,
12
+ type GestureAction,
13
+ type GestureArgs,
14
+ } from './schema.js';
9
15
  import { handleTap, handleDoubleTap, handleLongPress } from './handlers/tap.js';
10
16
  import { handleScroll, handleSwipe } from './handlers/swipe-scroll.js';
11
17
  import { handlePinchZoom } from './handlers/pinch.js';
12
18
  import { handleScrollToElement } from './handlers/scroll-to-element.js';
13
19
  import { back } from '../../command.js';
20
+ import {
21
+ withEvidence,
22
+ evidenceContext,
23
+ type EvidenceStage,
24
+ } from '../evidence.js';
25
+ import { isAiElementUUID } from './handlers/ai-element.js';
14
26
 
15
27
  export default function gesture(server: FastMCP): void {
16
28
  server.addTool({
@@ -35,31 +47,56 @@ export default function gesture(server: FastMCP): void {
35
47
  }
36
48
  const { driver } = resolved;
37
49
 
38
- switch (args.action) {
39
- case 'back':
40
- try {
41
- await back(driver);
42
- return textResult('Successfully performed back action.');
43
- } catch (err: unknown) {
44
- return errorResult(
45
- `Failed to perform back action. ${toolErrorMessage(err)}`
46
- );
47
- }
48
- case 'tap':
49
- return handleTap(driver, args);
50
- case 'double_tap':
51
- return handleDoubleTap(driver, args);
52
- case 'long_press':
53
- return handleLongPress(driver, args);
54
- case 'scroll':
55
- return handleScroll(driver, args);
56
- case 'swipe':
57
- return handleSwipe(driver, args);
58
- case 'pinch_zoom':
59
- return handlePinchZoom(driver, args);
60
- case 'scroll_to_element':
61
- return handleScrollToElement(driver, args);
62
- }
50
+ const startedAt = Date.now();
51
+ const context = await evidenceContext(args.sessionId);
52
+ const result = await dispatchGesture(driver, args);
53
+ return withEvidence(result, {
54
+ name: 'appium_gesture',
55
+ stage: gestureStage(args.action),
56
+ startedAt,
57
+ context,
58
+ ...(args.strategy && args.selector
59
+ ? { locator: { strategy: args.strategy, selector: args.selector } }
60
+ : {}),
61
+ ...(args.elementUUID && !isAiElementUUID(args.elementUUID)
62
+ ? { element: { webdriverId: args.elementUUID } }
63
+ : {}),
64
+ });
63
65
  },
64
66
  });
65
67
  }
68
+
69
+ async function dispatchGesture(
70
+ driver: DriverInstance,
71
+ args: GestureArgs
72
+ ): Promise<ContentResult> {
73
+ switch (args.action) {
74
+ case 'back':
75
+ try {
76
+ await back(driver);
77
+ return textResult('Successfully performed back action.');
78
+ } catch (err: unknown) {
79
+ return errorResult(
80
+ `Failed to perform back action. ${toolErrorMessage(err)}`
81
+ );
82
+ }
83
+ case 'tap':
84
+ return handleTap(driver, args);
85
+ case 'double_tap':
86
+ return handleDoubleTap(driver, args);
87
+ case 'long_press':
88
+ return handleLongPress(driver, args);
89
+ case 'scroll':
90
+ return handleScroll(driver, args);
91
+ case 'swipe':
92
+ return handleSwipe(driver, args);
93
+ case 'pinch_zoom':
94
+ return handlePinchZoom(driver, args);
95
+ case 'scroll_to_element':
96
+ return handleScrollToElement(driver, args);
97
+ }
98
+ }
99
+
100
+ function gestureStage(action: GestureAction): EvidenceStage {
101
+ return action === 'scroll_to_element' ? 'locate' : 'interact';
102
+ }
@@ -7,6 +7,7 @@ import {
7
7
  toolErrorMessage,
8
8
  readWebElementId,
9
9
  } from '../tool-response.js';
10
+ import { withEvidence, evidenceContext } from '../evidence.js';
10
11
 
11
12
  export const findElementSchema = z.object({
12
13
  strategy: z
@@ -74,19 +75,52 @@ export default function findElement(server: FastMCP): void {
74
75
  }
75
76
  const { driver } = resolved;
76
77
 
78
+ const startedAt = Date.now();
79
+ const locator = { strategy: args.strategy, selector: args.selector };
80
+ const context = await evidenceContext(args.sessionId);
77
81
  try {
78
82
  const element = await driver.findElement(args.strategy, args.selector);
79
83
  const elementId = readWebElementId(element);
80
84
  if (!elementId) {
81
- return errorResult('Element was returned without a valid element ID');
85
+ return withEvidence(
86
+ errorResult('Element was returned without a valid element ID'),
87
+ {
88
+ name: 'appium_find_element',
89
+ stage: 'locate',
90
+ startedAt,
91
+ locator,
92
+ context,
93
+ }
94
+ );
82
95
  }
83
- return textResultWithPrimaryElementId(
84
- elementId,
85
- `Successfully found element ${args.selector} with strategy ${args.strategy}.`
96
+ return withEvidence(
97
+ textResultWithPrimaryElementId(
98
+ elementId,
99
+ `Successfully found element ${args.selector} with strategy ${args.strategy}.`
100
+ ),
101
+ {
102
+ name: 'appium_find_element',
103
+ stage: 'locate',
104
+ startedAt,
105
+ locator,
106
+ element: { webdriverId: elementId },
107
+ context,
108
+ }
86
109
  );
87
110
  } catch (err: unknown) {
88
- const errorMessage = toolErrorMessage(err);
89
- return errorResult(`Failed to find element. Error: ${errorMessage}`);
111
+ return withEvidence(
112
+ errorResult(
113
+ `Failed to find element. Error: ${toolErrorMessage(err)}`
114
+ ),
115
+ {
116
+ name: 'appium_find_element',
117
+ stage: 'locate',
118
+ startedAt,
119
+ locator,
120
+ context,
121
+ error: err,
122
+ }
123
+ );
90
124
  }
91
125
  },
92
126
  });