@midscene/shared 1.0.1-beta-20251208031823.0 → 1.0.1-beta-20251208033501.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/dist/es/mcp/base-server.mjs +250 -0
- package/dist/es/mcp/base-tools.mjs +84 -0
- package/dist/es/mcp/index.mjs +4 -0
- package/dist/es/mcp/tool-generator.mjs +215 -0
- package/dist/es/mcp/types.mjs +3 -0
- package/dist/es/node/fs.mjs +1 -1
- package/dist/lib/baseDB.js +2 -2
- package/dist/lib/build/copy-static.js +2 -2
- package/dist/lib/build/rspack-config.js +2 -2
- package/dist/lib/common.js +2 -2
- package/dist/lib/constants/example-code.js +2 -2
- package/dist/lib/constants/index.js +2 -2
- package/dist/lib/env/basic.js +2 -2
- package/dist/lib/env/constants.js +2 -2
- package/dist/lib/env/global-config-manager.js +2 -2
- package/dist/lib/env/helper.js +2 -2
- package/dist/lib/env/index.js +6 -6
- package/dist/lib/env/init-debug.js +2 -2
- package/dist/lib/env/model-config-manager.js +2 -2
- package/dist/lib/env/parse-model-config.js +2 -2
- package/dist/lib/env/types.js +2 -2
- package/dist/lib/env/utils.js +2 -2
- package/dist/lib/extractor/constants.js +2 -2
- package/dist/lib/extractor/debug.js +1 -1
- package/dist/lib/extractor/dom-util.js +2 -2
- package/dist/lib/extractor/index.js +2 -2
- package/dist/lib/extractor/locator.js +2 -2
- package/dist/lib/extractor/tree.js +2 -2
- package/dist/lib/extractor/util.js +2 -2
- package/dist/lib/extractor/web-extractor.js +2 -2
- package/dist/lib/img/box-select.js +2 -2
- package/dist/lib/img/draw-box.js +2 -2
- package/dist/lib/img/get-jimp.js +2 -2
- package/dist/lib/img/get-photon.js +2 -2
- package/dist/lib/img/get-sharp.js +2 -2
- package/dist/lib/img/index.js +2 -2
- package/dist/lib/img/info.js +2 -2
- package/dist/lib/img/transform.js +2 -2
- package/dist/lib/index.js +2 -2
- package/dist/lib/logger.js +2 -2
- package/dist/lib/mcp/base-server.js +290 -0
- package/dist/lib/mcp/base-tools.js +118 -0
- package/dist/lib/mcp/index.js +79 -0
- package/dist/lib/mcp/tool-generator.js +252 -0
- package/dist/lib/mcp/types.js +40 -0
- package/dist/lib/node/fs.js +3 -3
- package/dist/lib/node/index.js +2 -2
- package/dist/lib/polyfills/async-hooks.js +2 -2
- package/dist/lib/polyfills/index.js +2 -2
- package/dist/lib/types/index.js +2 -2
- package/dist/lib/us-keyboard-layout.js +2 -2
- package/dist/lib/utils.js +2 -2
- package/dist/types/mcp/base-server.d.ts +77 -0
- package/dist/types/mcp/base-tools.d.ts +51 -0
- package/dist/types/mcp/index.d.ts +4 -0
- package/dist/types/mcp/tool-generator.d.ts +11 -0
- package/dist/types/mcp/types.d.ts +98 -0
- package/package.json +17 -3
- package/src/mcp/base-server.ts +432 -0
- package/src/mcp/base-tools.ts +190 -0
- package/src/mcp/index.ts +4 -0
- package/src/mcp/tool-generator.ts +311 -0
- package/src/mcp/types.ts +106 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { parseBase64 } from '@midscene/shared/img';
|
|
2
|
+
import { getDebug } from '@midscene/shared/logger';
|
|
3
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
4
|
+
import {
|
|
5
|
+
generateCommonTools,
|
|
6
|
+
generateToolsFromActionSpace,
|
|
7
|
+
} from './tool-generator';
|
|
8
|
+
import type {
|
|
9
|
+
ActionSpaceItem,
|
|
10
|
+
BaseAgent,
|
|
11
|
+
BaseDevice,
|
|
12
|
+
IMidsceneTools,
|
|
13
|
+
ToolDefinition,
|
|
14
|
+
} from './types';
|
|
15
|
+
|
|
16
|
+
const debug = getDebug('mcp:base-tools');
|
|
17
|
+
|
|
18
|
+
export abstract class BaseMidsceneTools implements IMidsceneTools {
|
|
19
|
+
protected mcpServer?: McpServer;
|
|
20
|
+
protected agent?: BaseAgent;
|
|
21
|
+
protected toolDefinitions: ToolDefinition[] = [];
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Ensure agent is initialized and ready for use.
|
|
25
|
+
* Must be implemented by subclasses to create platform-specific agent.
|
|
26
|
+
* @param initParam Optional initialization parameter (platform-specific, e.g., URL, device ID)
|
|
27
|
+
* @returns Promise resolving to initialized agent instance
|
|
28
|
+
* @throws Error if agent initialization fails
|
|
29
|
+
*/
|
|
30
|
+
protected abstract ensureAgent(initParam?: string): Promise<BaseAgent>;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Optional: prepare platform-specific tools (e.g., device connection)
|
|
34
|
+
*/
|
|
35
|
+
protected preparePlatformTools(): ToolDefinition[] {
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Must be implemented by subclasses to create a temporary device instance
|
|
41
|
+
* This allows getting real actionSpace without connecting to device
|
|
42
|
+
*/
|
|
43
|
+
protected abstract createTemporaryDevice(): BaseDevice;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Initialize all tools by querying actionSpace
|
|
47
|
+
* Uses two-layer fallback strategy:
|
|
48
|
+
* 1. Try to get actionSpace from connected agent (if available)
|
|
49
|
+
* 2. Create temporary device instance to read actionSpace (always succeeds)
|
|
50
|
+
*/
|
|
51
|
+
public async initTools(): Promise<void> {
|
|
52
|
+
this.toolDefinitions = [];
|
|
53
|
+
|
|
54
|
+
// 1. Add platform-specific tools first (device connection, etc.)
|
|
55
|
+
// These don't require an agent and should always be available
|
|
56
|
+
const platformTools = this.preparePlatformTools();
|
|
57
|
+
this.toolDefinitions.push(...platformTools);
|
|
58
|
+
|
|
59
|
+
// 2. Try to get agent and its action space (two-layer fallback)
|
|
60
|
+
let actionSpace: ActionSpaceItem[];
|
|
61
|
+
try {
|
|
62
|
+
// Layer 1: Try to use connected agent
|
|
63
|
+
const agent = await this.ensureAgent();
|
|
64
|
+
actionSpace = await agent.getActionSpace();
|
|
65
|
+
debug(
|
|
66
|
+
'Action space from connected agent:',
|
|
67
|
+
actionSpace.map((a) => a.name).join(', '),
|
|
68
|
+
);
|
|
69
|
+
} catch (error) {
|
|
70
|
+
// Layer 2: Create temporary device instance to read actionSpace
|
|
71
|
+
// This is expected behavior for bridge mode without URL or unconnected devices
|
|
72
|
+
const errorMessage =
|
|
73
|
+
error instanceof Error ? error.message : String(error);
|
|
74
|
+
if (
|
|
75
|
+
errorMessage.includes('requires a URL') ||
|
|
76
|
+
errorMessage.includes('web_connect')
|
|
77
|
+
) {
|
|
78
|
+
debug(
|
|
79
|
+
'Bridge mode detected - agent will be initialized on first web_connect call',
|
|
80
|
+
);
|
|
81
|
+
} else {
|
|
82
|
+
debug(
|
|
83
|
+
'Agent not available yet, using temporary device for action space',
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
const tempDevice = this.createTemporaryDevice();
|
|
87
|
+
actionSpace = tempDevice.actionSpace();
|
|
88
|
+
debug(
|
|
89
|
+
'Action space from temporary device:',
|
|
90
|
+
actionSpace.map((a) => a.name).join(', '),
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
// Destroy temporary instance using optional chaining
|
|
94
|
+
await tempDevice.destroy?.();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// 3. Generate tools from action space (core innovation)
|
|
98
|
+
const actionTools = generateToolsFromActionSpace(actionSpace, () =>
|
|
99
|
+
this.ensureAgent(),
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
// 4. Add common tools (screenshot, waitFor)
|
|
103
|
+
const commonTools = generateCommonTools(() => this.ensureAgent());
|
|
104
|
+
|
|
105
|
+
this.toolDefinitions.push(...actionTools, ...commonTools);
|
|
106
|
+
|
|
107
|
+
debug('Total tools prepared:', this.toolDefinitions.length);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Attach to MCP server and register all tools
|
|
112
|
+
*/
|
|
113
|
+
public attachToServer(server: McpServer): void {
|
|
114
|
+
this.mcpServer = server;
|
|
115
|
+
|
|
116
|
+
if (this.toolDefinitions.length === 0) {
|
|
117
|
+
debug('Warning: No tools to register. Tools may be initialized lazily.');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
for (const toolDef of this.toolDefinitions) {
|
|
121
|
+
if (toolDef.autoDestroy) {
|
|
122
|
+
this.toolWithAutoDestroy(
|
|
123
|
+
toolDef.name,
|
|
124
|
+
toolDef.description,
|
|
125
|
+
toolDef.schema,
|
|
126
|
+
toolDef.handler,
|
|
127
|
+
);
|
|
128
|
+
} else {
|
|
129
|
+
this.mcpServer.tool(
|
|
130
|
+
toolDef.name,
|
|
131
|
+
toolDef.description,
|
|
132
|
+
toolDef.schema,
|
|
133
|
+
toolDef.handler,
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
debug('Registered', this.toolDefinitions.length, 'tools');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Wrapper for auto-destroy behavior
|
|
143
|
+
*/
|
|
144
|
+
private toolWithAutoDestroy(
|
|
145
|
+
name: string,
|
|
146
|
+
description: string,
|
|
147
|
+
schema: any,
|
|
148
|
+
handler: (...args: any[]) => Promise<any>,
|
|
149
|
+
): void {
|
|
150
|
+
if (!this.mcpServer) {
|
|
151
|
+
throw new Error('MCP server not attached');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
this.mcpServer.tool(name, description, schema, async (...args: any[]) => {
|
|
155
|
+
try {
|
|
156
|
+
return await handler(...args);
|
|
157
|
+
} finally {
|
|
158
|
+
if (!process.env.MIDSCENE_MCP_DISABLE_AGENT_AUTO_DESTROY) {
|
|
159
|
+
try {
|
|
160
|
+
await this.agent?.destroy?.();
|
|
161
|
+
} catch (error) {
|
|
162
|
+
debug('Failed to destroy agent during cleanup:', error);
|
|
163
|
+
}
|
|
164
|
+
this.agent = undefined;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Cleanup method - destroy agent and release resources
|
|
172
|
+
*/
|
|
173
|
+
public async closeBrowser(): Promise<void> {
|
|
174
|
+
await this.agent?.destroy?.();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Helper: Convert base64 screenshot to image content array
|
|
179
|
+
*/
|
|
180
|
+
protected buildScreenshotContent(screenshot: string) {
|
|
181
|
+
const { mimeType, body } = parseBase64(screenshot);
|
|
182
|
+
return [
|
|
183
|
+
{
|
|
184
|
+
type: 'image' as const,
|
|
185
|
+
data: body,
|
|
186
|
+
mimeType,
|
|
187
|
+
},
|
|
188
|
+
];
|
|
189
|
+
}
|
|
190
|
+
}
|
package/src/mcp/index.ts
ADDED
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import { parseBase64 } from '@midscene/shared/img';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import type {
|
|
4
|
+
ActionSpaceItem,
|
|
5
|
+
BaseAgent,
|
|
6
|
+
ToolDefinition,
|
|
7
|
+
ToolResult,
|
|
8
|
+
} from './types';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Extract error message from unknown error type
|
|
12
|
+
*/
|
|
13
|
+
function getErrorMessage(error: unknown): string {
|
|
14
|
+
return error instanceof Error ? error.message : String(error);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Type guard: check if a Zod type is ZodOptional
|
|
19
|
+
*/
|
|
20
|
+
function isZodOptional(
|
|
21
|
+
value: z.ZodTypeAny,
|
|
22
|
+
): value is z.ZodOptional<z.ZodTypeAny> {
|
|
23
|
+
return '_def' in value && value._def?.typeName === 'ZodOptional';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Type guard: check if a Zod type is ZodObject
|
|
28
|
+
*/
|
|
29
|
+
function isZodObject(value: z.ZodTypeAny): value is z.ZodObject<z.ZodRawShape> {
|
|
30
|
+
return (
|
|
31
|
+
'_def' in value && value._def?.typeName === 'ZodObject' && 'shape' in value
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Unwrap ZodOptional to get inner type
|
|
37
|
+
*/
|
|
38
|
+
function unwrapOptional(value: z.ZodTypeAny): {
|
|
39
|
+
innerValue: z.ZodTypeAny;
|
|
40
|
+
isOptional: boolean;
|
|
41
|
+
} {
|
|
42
|
+
if (isZodOptional(value)) {
|
|
43
|
+
return { innerValue: value._def.innerType, isOptional: true };
|
|
44
|
+
}
|
|
45
|
+
return { innerValue: value, isOptional: false };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Check if a Zod object schema contains a 'prompt' field (locate field pattern)
|
|
50
|
+
*/
|
|
51
|
+
function isLocateField(value: z.ZodTypeAny): boolean {
|
|
52
|
+
if (!isZodObject(value)) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
return 'prompt' in value.shape;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Transform a locate field schema to make its 'prompt' field optional
|
|
60
|
+
*/
|
|
61
|
+
function makePromptOptional(
|
|
62
|
+
value: z.ZodObject<z.ZodRawShape>,
|
|
63
|
+
wrapInOptional: boolean,
|
|
64
|
+
): z.ZodTypeAny {
|
|
65
|
+
const newShape = { ...value.shape };
|
|
66
|
+
newShape.prompt = value.shape.prompt.optional();
|
|
67
|
+
|
|
68
|
+
let newSchema: z.ZodTypeAny = z.object(newShape).passthrough();
|
|
69
|
+
if (wrapInOptional) {
|
|
70
|
+
newSchema = newSchema.optional();
|
|
71
|
+
}
|
|
72
|
+
return newSchema;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Transform schema field to make locate.prompt optional if applicable
|
|
77
|
+
*/
|
|
78
|
+
function transformSchemaField(
|
|
79
|
+
key: string,
|
|
80
|
+
value: z.ZodTypeAny,
|
|
81
|
+
): [string, z.ZodTypeAny] {
|
|
82
|
+
const { innerValue, isOptional } = unwrapOptional(value);
|
|
83
|
+
|
|
84
|
+
if (isZodObject(innerValue) && isLocateField(innerValue)) {
|
|
85
|
+
return [key, makePromptOptional(innerValue, isOptional)];
|
|
86
|
+
}
|
|
87
|
+
return [key, value];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Extract and transform schema from action's paramSchema
|
|
92
|
+
*/
|
|
93
|
+
function extractActionSchema(
|
|
94
|
+
paramSchema: z.ZodTypeAny | undefined,
|
|
95
|
+
): Record<string, z.ZodTypeAny> {
|
|
96
|
+
if (!paramSchema) {
|
|
97
|
+
return {};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const schema = paramSchema as z.ZodTypeAny;
|
|
101
|
+
if (!isZodObject(schema)) {
|
|
102
|
+
return schema as unknown as Record<string, z.ZodTypeAny>;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return Object.fromEntries(
|
|
106
|
+
Object.entries(schema.shape).map(([key, value]) =>
|
|
107
|
+
transformSchemaField(key, value as z.ZodTypeAny),
|
|
108
|
+
),
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Serialize args to human-readable description for AI action
|
|
114
|
+
*/
|
|
115
|
+
function serializeArgsToDescription(args: Record<string, unknown>): string {
|
|
116
|
+
try {
|
|
117
|
+
return Object.entries(args)
|
|
118
|
+
.map(([key, value]) => {
|
|
119
|
+
if (typeof value === 'object' && value !== null) {
|
|
120
|
+
try {
|
|
121
|
+
return `${key}: ${JSON.stringify(value)}`;
|
|
122
|
+
} catch {
|
|
123
|
+
// Circular reference or non-serializable object
|
|
124
|
+
return `${key}: [object]`;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return `${key}: "${value}"`;
|
|
128
|
+
})
|
|
129
|
+
.join(', ');
|
|
130
|
+
} catch (error: unknown) {
|
|
131
|
+
const errorMessage = getErrorMessage(error);
|
|
132
|
+
console.error('Error serializing args:', errorMessage);
|
|
133
|
+
return `[args serialization failed: ${errorMessage}]`;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Build action instruction string from action name and args
|
|
139
|
+
*/
|
|
140
|
+
function buildActionInstruction(
|
|
141
|
+
actionName: string,
|
|
142
|
+
args: Record<string, unknown>,
|
|
143
|
+
): string {
|
|
144
|
+
const argsDescription = serializeArgsToDescription(args);
|
|
145
|
+
return argsDescription
|
|
146
|
+
? `Use the action "${actionName}" with ${argsDescription}`
|
|
147
|
+
: `Use the action "${actionName}"`;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Capture screenshot and return as tool result
|
|
152
|
+
*/
|
|
153
|
+
async function captureScreenshotResult(
|
|
154
|
+
agent: BaseAgent,
|
|
155
|
+
actionName: string,
|
|
156
|
+
): Promise<ToolResult> {
|
|
157
|
+
try {
|
|
158
|
+
const screenshot = await agent.page?.screenshotBase64();
|
|
159
|
+
if (!screenshot) {
|
|
160
|
+
return {
|
|
161
|
+
content: [{ type: 'text', text: `Action "${actionName}" completed.` }],
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const { mimeType, body } = parseBase64(screenshot);
|
|
166
|
+
return {
|
|
167
|
+
content: [
|
|
168
|
+
{ type: 'text', text: `Action "${actionName}" completed.` },
|
|
169
|
+
{ type: 'image', data: body, mimeType },
|
|
170
|
+
],
|
|
171
|
+
};
|
|
172
|
+
} catch (error: unknown) {
|
|
173
|
+
const errorMessage = getErrorMessage(error);
|
|
174
|
+
console.error('Error capturing screenshot:', errorMessage);
|
|
175
|
+
return {
|
|
176
|
+
content: [
|
|
177
|
+
{
|
|
178
|
+
type: 'text',
|
|
179
|
+
text: `Action "${actionName}" completed (screenshot unavailable: ${errorMessage})`,
|
|
180
|
+
},
|
|
181
|
+
],
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Create error result for tool handler
|
|
188
|
+
*/
|
|
189
|
+
function createErrorResult(message: string): ToolResult {
|
|
190
|
+
return {
|
|
191
|
+
content: [{ type: 'text', text: message }],
|
|
192
|
+
isError: true,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Converts DeviceAction from actionSpace into MCP ToolDefinition
|
|
198
|
+
* This is the core logic that removes need for hardcoded tool definitions
|
|
199
|
+
*/
|
|
200
|
+
export function generateToolsFromActionSpace(
|
|
201
|
+
actionSpace: ActionSpaceItem[],
|
|
202
|
+
getAgent: () => Promise<BaseAgent>,
|
|
203
|
+
): ToolDefinition[] {
|
|
204
|
+
return actionSpace.map((action) => {
|
|
205
|
+
const schema = extractActionSchema(action.paramSchema as z.ZodTypeAny);
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
name: action.name,
|
|
209
|
+
description: action.description || `Execute ${action.name} action`,
|
|
210
|
+
schema,
|
|
211
|
+
handler: async (args: Record<string, unknown>) => {
|
|
212
|
+
try {
|
|
213
|
+
const agent = await getAgent();
|
|
214
|
+
|
|
215
|
+
if (agent.aiAction) {
|
|
216
|
+
const instruction = buildActionInstruction(action.name, args);
|
|
217
|
+
try {
|
|
218
|
+
await agent.aiAction(instruction);
|
|
219
|
+
} catch (error: unknown) {
|
|
220
|
+
const errorMessage = getErrorMessage(error);
|
|
221
|
+
console.error(
|
|
222
|
+
`Error executing action "${action.name}":`,
|
|
223
|
+
errorMessage,
|
|
224
|
+
);
|
|
225
|
+
return createErrorResult(
|
|
226
|
+
`Failed to execute action "${action.name}": ${errorMessage}`,
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return await captureScreenshotResult(agent, action.name);
|
|
232
|
+
} catch (error: unknown) {
|
|
233
|
+
const errorMessage = getErrorMessage(error);
|
|
234
|
+
console.error(`Error in handler for "${action.name}":`, errorMessage);
|
|
235
|
+
return createErrorResult(
|
|
236
|
+
`Failed to get agent or execute action "${action.name}": ${errorMessage}`,
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
autoDestroy: true,
|
|
241
|
+
};
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Generate common tools (screenshot, waitFor)
|
|
247
|
+
* SIMPLIFIED: Only keep essential helper tools, removed assert
|
|
248
|
+
*/
|
|
249
|
+
export function generateCommonTools(
|
|
250
|
+
getAgent: () => Promise<BaseAgent>,
|
|
251
|
+
): ToolDefinition[] {
|
|
252
|
+
return [
|
|
253
|
+
{
|
|
254
|
+
name: 'take_screenshot',
|
|
255
|
+
description: 'Capture screenshot of current page/screen',
|
|
256
|
+
schema: {},
|
|
257
|
+
handler: async (): Promise<ToolResult> => {
|
|
258
|
+
try {
|
|
259
|
+
const agent = await getAgent();
|
|
260
|
+
const screenshot = await agent.page?.screenshotBase64();
|
|
261
|
+
if (!screenshot) {
|
|
262
|
+
return createErrorResult('Screenshot not available');
|
|
263
|
+
}
|
|
264
|
+
const { mimeType, body } = parseBase64(screenshot);
|
|
265
|
+
return {
|
|
266
|
+
content: [{ type: 'image', data: body, mimeType }],
|
|
267
|
+
};
|
|
268
|
+
} catch (error: unknown) {
|
|
269
|
+
const errorMessage = getErrorMessage(error);
|
|
270
|
+
console.error('Error taking screenshot:', errorMessage);
|
|
271
|
+
return createErrorResult(
|
|
272
|
+
`Failed to capture screenshot: ${errorMessage}`,
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
autoDestroy: true,
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
name: 'wait_for',
|
|
280
|
+
description: 'Wait until condition becomes true',
|
|
281
|
+
schema: {
|
|
282
|
+
assertion: z.string().describe('Condition to wait for'),
|
|
283
|
+
timeoutMs: z.number().optional().default(15000),
|
|
284
|
+
checkIntervalMs: z.number().optional().default(3000),
|
|
285
|
+
},
|
|
286
|
+
handler: async (args): Promise<ToolResult> => {
|
|
287
|
+
try {
|
|
288
|
+
const agent = await getAgent();
|
|
289
|
+
const { assertion, timeoutMs, checkIntervalMs } = args as {
|
|
290
|
+
assertion: string;
|
|
291
|
+
timeoutMs?: number;
|
|
292
|
+
checkIntervalMs?: number;
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
if (agent.aiWaitFor) {
|
|
296
|
+
await agent.aiWaitFor(assertion, { timeoutMs, checkIntervalMs });
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return {
|
|
300
|
+
content: [{ type: 'text', text: `Condition met: "${assertion}"` }],
|
|
301
|
+
};
|
|
302
|
+
} catch (error: unknown) {
|
|
303
|
+
const errorMessage = getErrorMessage(error);
|
|
304
|
+
console.error('Error in wait_for:', errorMessage);
|
|
305
|
+
return createErrorResult(`Wait condition failed: ${errorMessage}`);
|
|
306
|
+
}
|
|
307
|
+
},
|
|
308
|
+
autoDestroy: true,
|
|
309
|
+
},
|
|
310
|
+
];
|
|
311
|
+
}
|
package/src/mcp/types.ts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import type { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
// Avoid circular dependency: don't import from @midscene/core
|
|
5
|
+
// Instead, use generic types that will be provided by implementation
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Default timeout constants for app loading verification
|
|
9
|
+
*/
|
|
10
|
+
export const defaultAppLoadingTimeoutMs = 10000;
|
|
11
|
+
export const defaultAppLoadingCheckIntervalMs = 2000;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Content item types for tool results (MCP compatible)
|
|
15
|
+
*/
|
|
16
|
+
export type ToolResultContent =
|
|
17
|
+
| { type: 'text'; text: string }
|
|
18
|
+
| { type: 'image'; data: string; mimeType: string }
|
|
19
|
+
| { type: 'audio'; data: string; mimeType: string }
|
|
20
|
+
| {
|
|
21
|
+
type: 'resource';
|
|
22
|
+
resource:
|
|
23
|
+
| { text: string; uri: string; mimeType?: string }
|
|
24
|
+
| { uri: string; blob: string; mimeType?: string };
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Result type for tool execution (MCP compatible)
|
|
29
|
+
*/
|
|
30
|
+
export interface ToolResult {
|
|
31
|
+
[x: string]: unknown;
|
|
32
|
+
content: ToolResultContent[];
|
|
33
|
+
isError?: boolean;
|
|
34
|
+
_meta?: Record<string, unknown>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Tool handler function type
|
|
39
|
+
* Takes parsed arguments and returns a tool result
|
|
40
|
+
*/
|
|
41
|
+
export type ToolHandler<T = Record<string, unknown>> = (
|
|
42
|
+
args: T,
|
|
43
|
+
) => Promise<ToolResult>;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Tool schema type using Zod
|
|
47
|
+
*/
|
|
48
|
+
export type ToolSchema = Record<string, z.ZodTypeAny>;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Tool definition for MCP server
|
|
52
|
+
*/
|
|
53
|
+
export interface ToolDefinition<T = Record<string, unknown>> {
|
|
54
|
+
name: string;
|
|
55
|
+
description: string;
|
|
56
|
+
schema: ToolSchema;
|
|
57
|
+
handler: ToolHandler<T>;
|
|
58
|
+
autoDestroy?: boolean;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Action space item definition
|
|
63
|
+
*/
|
|
64
|
+
export interface ActionSpaceItem {
|
|
65
|
+
name: string;
|
|
66
|
+
description?: string;
|
|
67
|
+
args?: Record<string, unknown>;
|
|
68
|
+
[key: string]: unknown;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Base agent interface
|
|
73
|
+
* Represents a platform-specific agent (Android, iOS, Web)
|
|
74
|
+
*/
|
|
75
|
+
export interface BaseAgent {
|
|
76
|
+
getActionSpace(): Promise<ActionSpaceItem[]>;
|
|
77
|
+
destroy?(): Promise<void>;
|
|
78
|
+
page?: {
|
|
79
|
+
screenshotBase64(): Promise<string>;
|
|
80
|
+
};
|
|
81
|
+
aiAction?: (
|
|
82
|
+
description: string,
|
|
83
|
+
params?: Record<string, unknown>,
|
|
84
|
+
) => Promise<void>;
|
|
85
|
+
aiWaitFor?: (
|
|
86
|
+
assertion: string,
|
|
87
|
+
options: Record<string, unknown>,
|
|
88
|
+
) => Promise<void>;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Base device interface for temporary device instances
|
|
93
|
+
*/
|
|
94
|
+
export interface BaseDevice {
|
|
95
|
+
actionSpace(): ActionSpaceItem[];
|
|
96
|
+
destroy?(): Promise<void>;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Interface for platform-specific MCP tools manager
|
|
101
|
+
*/
|
|
102
|
+
export interface IMidsceneTools {
|
|
103
|
+
attachToServer(server: McpServer): void;
|
|
104
|
+
initTools(): Promise<void>;
|
|
105
|
+
closeBrowser?(): Promise<void>;
|
|
106
|
+
}
|