ucu-mcp 0.3.6 → 0.3.7
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 +18 -0
- package/dist/src/mcp/tools.d.ts +21 -0
- package/dist/src/mcp/tools.js +19 -9
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,23 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.3.7] - 2026-06-07
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- `find_element` value-schema test is no longer a tautology. The 0.3.6 release fixed a *symptom* of the bug (the old test called `handler()` directly, bypassing the McpServer schema-validation wrapper, and then asserted `r.isError === true` which was `undefined`); the underlying tautology remained: the test re-created a local `z.string().min(1).optional()` instead of exercising the real schema. 0.3.7 exports the actual `findElementInputSchema` from `src/mcp/tools.ts` and the test now imports it via `findElementInputSchema.value`, so the assertion genuinely pins the production schema. Pins the 0.3.2 commit `46d4ddd` semantic.
|
|
13
|
+
- CHANGELOG/JXA `textMatches` comment math is now correct: 3 sources → 1 RegExp = **2 fewer** compilations per matched element. The 0.3.5/0.3.6 wording "three fewer" was off by one and has been corrected in both `src/platform/macos.ts` and this CHANGELOG.
|
|
14
|
+
|
|
15
|
+
### Tests
|
|
16
|
+
|
|
17
|
+
- `macos-platform`: text-side regex pre-validation now has a regression test mirroring the existing value-side test (`findElement({text:"[", textMode:"regex"})` throws `PlatformError` with an `Invalid regex pattern` message). Pins the original text-side guard that the 0.3.2 commit mirrored onto the value side.
|
|
18
|
+
- `macos-platform`: all-no-bounds edge case for the `near` sort — when every result is missing `bounds`, the original JXA order is preserved. Pins the 0.3.2 commit `0710eca` no-bounds fallback against a future refactor that introduces a non-stable comparator.
|
|
19
|
+
|
|
20
|
+
### Hygiene
|
|
21
|
+
|
|
22
|
+
- `findElementInputSchema` is now a named export from `src/mcp/tools.ts` (with a JSDoc comment explaining why the schema is exported) so the unit test can assert the production schema directly instead of constructing a local copy.
|
|
23
|
+
- Added `prepublishOnly` script to `package.json` that runs `npm test && npm run build` before `npm publish`. This is a structural guard against the yank rhythm that hit 0.3.3 and 0.3.5: a failed test or build will now block the publish at the npm level, not at the human level. (Raman review Minor #3)
|
|
24
|
+
|
|
8
25
|
## [0.3.5] - 2026-06-06 *(Yanked — see 0.3.6)*
|
|
9
26
|
|
|
10
27
|
### Tests
|
|
@@ -16,6 +33,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
16
33
|
### Changed
|
|
17
34
|
|
|
18
35
|
- JXA `textMatches` regex branch now compiles the `RegExp` once per element instead of once per source (name / value / description) — three fewer compilations per matched element when `textMode="regex"`. The TS-side pre-validation in `findElement` guarantees the pattern is valid, so the `RegExp` constructor cannot throw here. (Herschel review perf Minor)
|
|
36
|
+
- JXA `textMatches` regex branch now compiles the `RegExp` once per element instead of once per source (name / value / description) — **two** fewer compilations per matched element when `textMode="regex"` (corrected in 0.3.7; 0.3.5/0.3.6 said "three fewer" which was off by one: 3 sources → 1 regex = 2 saved). The TS-side pre-validation in `findElement` guarantees the pattern is valid, so the `RegExp` constructor cannot throw here. (Herschel review perf Minor)
|
|
19
37
|
|
|
20
38
|
### Fixed
|
|
21
39
|
|
package/dist/src/mcp/tools.d.ts
CHANGED
|
@@ -5,11 +5,32 @@
|
|
|
5
5
|
* a shared safety/permission/retry pipeline (`withSafety`).
|
|
6
6
|
*/
|
|
7
7
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
8
|
+
import { z } from "zod";
|
|
8
9
|
import type { AppTarget } from "../platform/base.js";
|
|
9
10
|
/**
|
|
10
11
|
* Get the currently active target context (set by focus_app).
|
|
11
12
|
*/
|
|
12
13
|
export declare function getActiveTarget(): AppTarget | undefined;
|
|
14
|
+
export declare const findElementInputSchema: {
|
|
15
|
+
text: z.ZodOptional<z.ZodString>;
|
|
16
|
+
role: z.ZodOptional<z.ZodString>;
|
|
17
|
+
app: z.ZodOptional<z.ZodString>;
|
|
18
|
+
depth: z.ZodOptional<z.ZodNumber>;
|
|
19
|
+
includeBounds: z.ZodDefault<z.ZodBoolean>;
|
|
20
|
+
maxResults: z.ZodDefault<z.ZodNumber>;
|
|
21
|
+
textMode: z.ZodDefault<z.ZodEnum<{
|
|
22
|
+
contains: "contains";
|
|
23
|
+
exact: "exact";
|
|
24
|
+
regex: "regex";
|
|
25
|
+
}>>;
|
|
26
|
+
visibleOnly: z.ZodDefault<z.ZodBoolean>;
|
|
27
|
+
value: z.ZodOptional<z.ZodString>;
|
|
28
|
+
index: z.ZodOptional<z.ZodNumber>;
|
|
29
|
+
near: z.ZodOptional<z.ZodObject<{
|
|
30
|
+
x: z.ZodNumber;
|
|
31
|
+
y: z.ZodNumber;
|
|
32
|
+
}, z.core.$strip>>;
|
|
33
|
+
};
|
|
13
34
|
export declare function startUserActivityMonitor(): void;
|
|
14
35
|
export declare function stopUserActivityMonitor(): void;
|
|
15
36
|
export declare function registerTools(server: McpServer): void;
|
package/dist/src/mcp/tools.js
CHANGED
|
@@ -37,6 +37,24 @@ const captureAfterFields = {
|
|
|
37
37
|
captureMaxWidth: z.number().default(1280).describe("Maximum width for the post-action screenshot"),
|
|
38
38
|
captureFormat: z.enum(["png", "jpeg"]).default("jpeg").describe("Format for the post-action screenshot"),
|
|
39
39
|
};
|
|
40
|
+
// Exported so unit tests can pin the schema constraint directly instead
|
|
41
|
+
// of going through the McpServer wrapper (which `handler()` calls
|
|
42
|
+
// bypass). (Herschel review Major: 0.3.5's value='' test was a
|
|
43
|
+
// tautology because it re-created a local zod schema instead of
|
|
44
|
+
// asserting against this one.)
|
|
45
|
+
export const findElementInputSchema = {
|
|
46
|
+
text: z.string().optional().describe("Text to search"),
|
|
47
|
+
role: z.string().optional().describe("AX role"),
|
|
48
|
+
app: z.string().optional().describe("Target app"),
|
|
49
|
+
depth: z.number().optional().describe("AX tree depth"),
|
|
50
|
+
includeBounds: z.boolean().default(true).describe("Include bounds"),
|
|
51
|
+
maxResults: z.number().min(1).max(200).default(50).describe("Max results"),
|
|
52
|
+
textMode: z.enum(["contains", "exact", "regex"]).default("contains").describe("Text matching mode: contains (default), exact, or regex"),
|
|
53
|
+
visibleOnly: z.boolean().default(false).describe("Only return elements with valid on-screen bounds"),
|
|
54
|
+
value: z.string().min(1).optional().describe("Filter by AX element value (text/regex/exact, see textMode). Empty string is treated as unset (omit the field instead)."),
|
|
55
|
+
index: z.number().int().nonnegative().optional().describe("Return only the Nth match (0-based) after all other filtering and sorting"),
|
|
56
|
+
near: z.object({ x: z.number(), y: z.number() }).optional().describe("Sort results by ascending distance to this point and return closest first"),
|
|
57
|
+
};
|
|
40
58
|
async function resolvePoint(x, y, windowId) {
|
|
41
59
|
if (!windowId)
|
|
42
60
|
return { x, y };
|
|
@@ -547,15 +565,7 @@ export function registerTools(server) {
|
|
|
547
565
|
return actionResponse("move", { moved: true, x: pt.x, y: pt.y }, { x: pt.x, y: pt.y, windowId: params.windowId }, params.captureAfter, params.captureFormat, params.captureMaxWidth);
|
|
548
566
|
});
|
|
549
567
|
registry.register("move");
|
|
550
|
-
registerTool("find_element", "Find accessibility elements by text, role, or value. Supports value/index/near selectors.", {
|
|
551
|
-
text: z.string().optional().describe("Text to search"), role: z.string().optional().describe("AX role"), app: z.string().optional().describe("Target app"),
|
|
552
|
-
depth: z.number().optional().describe("AX tree depth"), includeBounds: z.boolean().default(true).describe("Include bounds"), maxResults: z.number().min(1).max(200).default(50).describe("Max results"),
|
|
553
|
-
textMode: z.enum(["contains", "exact", "regex"]).default("contains").describe("Text matching mode: contains (default), exact, or regex"),
|
|
554
|
-
visibleOnly: z.boolean().default(false).describe("Only return elements with valid on-screen bounds"),
|
|
555
|
-
value: z.string().min(1).optional().describe("Filter by AX element value (text/regex/exact, see textMode). Empty string is treated as unset (omit the field instead)."),
|
|
556
|
-
index: z.number().int().nonnegative().optional().describe("Return only the Nth match (0-based) after all other filtering and sorting"),
|
|
557
|
-
near: z.object({ x: z.number(), y: z.number() }).optional().describe("Sort results by ascending distance to this point and return closest first"),
|
|
558
|
-
}, async (params) => {
|
|
568
|
+
registerTool("find_element", "Find accessibility elements by text, role, or value. Supports value/index/near selectors.", findElementInputSchema, async (params) => {
|
|
559
569
|
const effectiveApp = params.app || getActiveTarget()?.appName;
|
|
560
570
|
const response = await withSafety({ action: "find_element", params: {}, requiresAccessibility: true,
|
|
561
571
|
execute: () => getPlatform().findElement({ text: params.text, role: params.role, app: effectiveApp, depth: params.depth, includeBounds: params.includeBounds, maxResults: params.maxResults, textMode: params.textMode, visibleOnly: params.visibleOnly, value: params.value, index: params.index, near: params.near }) });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ucu-mcp",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.7",
|
|
4
4
|
"description": "MCP server for Universal Computer Use — desktop automation for AI agents via Model Context Protocol",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"test:integration": "vitest run tests/integration/",
|
|
27
27
|
"test:macos-gui": "UCU_MACOS_GUI_SMOKE=1 vitest run tests/integration/macos-gui-smoke.test.ts",
|
|
28
28
|
"test:client-cli": "UCU_CLIENT_CLI_SMOKE=1 vitest run tests/integration/client-cli-smoke.test.ts",
|
|
29
|
+
"prepublishOnly": "npx vitest run tests/unit/ && npm run build",
|
|
29
30
|
"build:native": "cd native/cgevent && swiftc -O -o cgevent-helper main.swift -framework CoreGraphics -framework Foundation && cd ../ocr && swiftc -O -o ocr-helper main.swift -framework Vision -framework AppKit"
|
|
30
31
|
},
|
|
31
32
|
"keywords": [
|