@robotyxx/robotyx-mcp 0.1.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/README.md ADDED
@@ -0,0 +1,76 @@
1
+ # robotyx-mcp
2
+
3
+ Stdio MCP server that wraps Robotyx's REST API. Lets Claude Code / Claude
4
+ Desktop browse recorded workflows, refine their TestSteps into grouped
5
+ functional steps ("clicks 1-7 → Log in as admin"), and push the result back
6
+ — without leaving the chat.
7
+
8
+ No new business logic lives here. Every tool is a thin wrapper around an
9
+ existing endpoint the Robotyx web UI also uses.
10
+
11
+ ## Tools
12
+
13
+ | Tool | Wraps | Notes |
14
+ |------|-------|-------|
15
+ | `list_workflows` | `GET /api/workflows` | Summary rows. |
16
+ | `get_workflow` | `GET /api/workflows/:id` | Screenshots stripped by default. Pass `include_screenshots: "all"` or `"indices"` + `screenshot_step_indices: [1,3,5]` when you need visual context. |
17
+ | `list_testsuites` | `GET /api/testsuites` | |
18
+ | `get_testsuite` | `GET /api/testsuites/:id` | Includes cases + their steps. |
19
+ | `create_testsuite` | `POST /api/testsuites` | |
20
+ | `add_test_case` | `POST /api/testsuites/:id/cases` | Optionally links a Workflow id. |
21
+ | `set_test_steps` | `POST /api/testsuites/:id/cases/:caseId/import-mapping` | The refine path. Body shape mirrors the UI's Excel import — pass `workflow_step_ids` (full ordered list from `get_workflow`) + `mappings: [{description, expected_result, workflow_step_indices: [1,2,3]}]`. |
22
+ | `merge_step` | `POST .../steps/:stepId/merge` | |
23
+ | `split_step` | `POST .../steps/:stepId/split` | |
24
+ | `create_defect` | `POST /api/defects` | |
25
+
26
+ ## Wire-up — Claude Code
27
+
28
+ Drop a `.mcp.json` in your workspace root (already done for
29
+ `ai-automation-platform/` and `footprint-platform/`):
30
+
31
+ ```json
32
+ {
33
+ "mcpServers": {
34
+ "robotyx": {
35
+ "command": "npx",
36
+ "args": ["-y", "tsx", "C:\\Users\\…\\ai-automation-platform\\robotyx-mcp\\src\\index.ts"],
37
+ "env": {
38
+ "ROBOTYX_URL": "http://localhost:3001",
39
+ "ROBOTYX_API_KEY": "${ROBOTYX_API_KEY}"
40
+ }
41
+ }
42
+ }
43
+ }
44
+ ```
45
+
46
+ Then in PowerShell (once per machine):
47
+
48
+ ```powershell
49
+ [Environment]::SetEnvironmentVariable("ROBOTYX_API_KEY", "rx_…", "User")
50
+ ```
51
+
52
+ Restart Claude Code so it picks up the new MCP. Verify with `/mcp` — you
53
+ should see `robotyx: ✓ connected (10 tools)`.
54
+
55
+ ## Wire-up — Claude Desktop
56
+
57
+ Add the same `mcpServers` block to
58
+ `%APPDATA%\Claude\claude_desktop_config.json` and restart the app.
59
+
60
+ ## Typical refine loop
61
+
62
+ 1. `list_workflows` → pick a workflow id.
63
+ 2. `get_workflow` (no screenshots) → AI proposes a grouping based on
64
+ step descriptions + action types.
65
+ 3. `get_workflow` with `include_screenshots: "indices"` and the ambiguous
66
+ step indices → AI disambiguates visually.
67
+ 4. Either `add_test_case` (linking the workflow) or pick an existing one
68
+ from `get_testsuite`.
69
+ 5. `set_test_steps` with the proposed mapping → done.
70
+
71
+ ## Token budget
72
+
73
+ A 30-step browser recording with full-page PNGs is ~200-400k tokens with
74
+ `include_screenshots: "all"`. Always start with `"none"`, then use
75
+ `"indices"` to fetch only the screenshots you need. The server strips
76
+ `footprint_*` bookkeeping keys (raw a11y dumps, console logs) unconditionally.
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Gateway tool bridge — exposes ALL of the gateway's aggregated MCP tools
3
+ * (autopilot / selenium / electron / playwright / java / api-agent …) to an
4
+ * external MCP client (Claude Code) through a single server, and forwards every
5
+ * call to the gateway's POST /api/tools/call.
6
+ *
7
+ * Why a bridge instead of wiring each MCP into the client directly: tool calls
8
+ * must flow through the gateway so the gateway's RecordingManager captures them
9
+ * into the active workflow (see /api/tools/call). Calling the underlying MCP
10
+ * servers directly would bypass recording — and we'd have to solve recording
11
+ * per-client. One bridge → all tools, all recorded, all replayable by the
12
+ * PlaybackEngine.
13
+ *
14
+ * Env:
15
+ * ROBOTYX_URL — gateway base URL (default http://localhost:3001)
16
+ * ROBOTYX_API_KEY — long-lived `rx_…` Bearer token (required)
17
+ *
18
+ * .mcp.json:
19
+ * "gateway-tools": {
20
+ * "command": "npx",
21
+ * "args": ["-y", "tsx", "…/robotyx-mcp/src/gateway-bridge.ts"],
22
+ * "env": { "ROBOTYX_URL": "http://localhost:3001", "ROBOTYX_API_KEY": "rx_…" }
23
+ * }
24
+ */
25
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
26
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
27
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
28
+ const ROBOTYX_URL = (process.env.ROBOTYX_URL || "http://localhost:3001").replace(/\/+$/, "");
29
+ const ROBOTYX_API_KEY = process.env.ROBOTYX_API_KEY || "";
30
+ if (!ROBOTYX_API_KEY) {
31
+ process.stderr.write("[gateway-bridge] ROBOTYX_API_KEY is not set. Mint a key in the Robotyx UI " +
32
+ "(Settings → API keys) and set it in the MCP env. Exiting.\n");
33
+ process.exit(1);
34
+ }
35
+ async function fetchTools() {
36
+ const res = await fetch(`${ROBOTYX_URL}/api/tools`, {
37
+ headers: { Authorization: `Bearer ${ROBOTYX_API_KEY}` },
38
+ });
39
+ if (!res.ok) {
40
+ throw new Error(`GET /api/tools → ${res.status} ${await res.text().catch(() => "")}`);
41
+ }
42
+ const data = (await res.json());
43
+ return data.tools ?? [];
44
+ }
45
+ /** Forward a tool call to the gateway; it routes + records + returns the MCP result. */
46
+ async function callTool(name, args) {
47
+ const res = await fetch(`${ROBOTYX_URL}/api/tools/call`, {
48
+ method: "POST",
49
+ headers: {
50
+ Authorization: `Bearer ${ROBOTYX_API_KEY}`,
51
+ "Content-Type": "application/json",
52
+ },
53
+ body: JSON.stringify({ name, args }),
54
+ });
55
+ const text = await res.text();
56
+ if (!res.ok) {
57
+ return { isError: true, content: [{ type: "text", text: `${res.status}: ${text}` }] };
58
+ }
59
+ // The gateway returns the underlying MCP result ({ content, isError }) verbatim.
60
+ try {
61
+ const parsed = JSON.parse(text);
62
+ if (parsed && Array.isArray(parsed.content))
63
+ return parsed;
64
+ return { content: [{ type: "text", text }] };
65
+ }
66
+ catch {
67
+ return { content: [{ type: "text", text }] };
68
+ }
69
+ }
70
+ const server = new Server({ name: "gateway-tools", version: "0.1.0" }, { capabilities: { tools: {} } });
71
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
72
+ const tools = await fetchTools();
73
+ return {
74
+ tools: tools.map((t) => ({
75
+ name: t.name,
76
+ description: `[${t.server}] ${t.description || ""}`.trim(),
77
+ inputSchema: t.inputSchema || { type: "object" },
78
+ })),
79
+ };
80
+ });
81
+ server.setRequestHandler(CallToolRequestSchema, async (req) => {
82
+ const { name, arguments: args } = req.params;
83
+ try {
84
+ return await callTool(name, (args ?? {}));
85
+ }
86
+ catch (err) {
87
+ return { isError: true, content: [{ type: "text", text: err?.message ?? String(err) }] };
88
+ }
89
+ });
90
+ const transport = new StdioServerTransport();
91
+ await server.connect(transport);
92
+ process.stderr.write(`[gateway-bridge] connected, target ${ROBOTYX_URL}\n`);