@minicor/mcp-server 2.0.3 → 2.0.5
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 +125 -124
- package/dist/__tests__/helpers.test.js +3 -3
- package/dist/__tests__/tfa-client.test.d.ts +2 -0
- package/dist/__tests__/tfa-client.test.d.ts.map +1 -0
- package/dist/__tests__/tfa-client.test.js +191 -0
- package/dist/__tests__/tfa-client.test.js.map +1 -0
- package/dist/helpers.d.ts +2 -2
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +7 -4
- package/dist/helpers.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/inspect-scripts.d.ts +1 -1
- package/dist/inspect-scripts.js +1 -1
- package/dist/lds-client.d.ts +1 -1
- package/dist/lds-client.js +2 -2
- package/dist/prompts/build-rpa.d.ts.map +1 -1
- package/dist/prompts/build-rpa.js +37 -9
- package/dist/prompts/build-rpa.js.map +1 -1
- package/dist/prompts/tfa-guide.d.ts +3 -0
- package/dist/prompts/tfa-guide.d.ts.map +1 -0
- package/dist/prompts/tfa-guide.js +178 -0
- package/dist/prompts/tfa-guide.js.map +1 -0
- package/dist/tfa-client.d.ts +45 -0
- package/dist/tfa-client.d.ts.map +1 -0
- package/dist/tfa-client.js +116 -0
- package/dist/tfa-client.js.map +1 -0
- package/dist/tools/tfa.d.ts +3 -0
- package/dist/tools/tfa.d.ts.map +1 -0
- package/dist/tools/tfa.js +146 -0
- package/dist/tools/tfa.js.map +1 -0
- package/dist/tools/vm-rpa.js +12 -12
- package/dist/tools/vm-rpa.js.map +1 -1
- package/dist/tools/vm.js +4 -4
- package/dist/tools/workflow-ops.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,20 +1,12 @@
|
|
|
1
1
|
# Minicor MCP Server
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Build, deploy, and debug VM-based browser and desktop automations from **Cursor** or **Claude Code**. The AI agent connects to your Windows VM, writes Python automation scripts, tests them live, and saves them as Minicor workflows.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Quick Start
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
### Cursor
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
npm install -g @minicor/mcp-server
|
|
11
|
-
minicor-mcp-setup
|
|
12
|
-
claude mcp add minicor -- minicor-mcp
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
## Add to Cursor
|
|
16
|
-
|
|
17
|
-
Run the setup, then add to your Cursor MCP config (`~/.cursor/mcp.json`):
|
|
9
|
+
Run the setup wizard, then add to your Cursor MCP config (`~/.cursor/mcp.json`):
|
|
18
10
|
|
|
19
11
|
```json
|
|
20
12
|
{
|
|
@@ -27,119 +19,149 @@ Run the setup, then add to your Cursor MCP config (`~/.cursor/mcp.json`):
|
|
|
27
19
|
}
|
|
28
20
|
```
|
|
29
21
|
|
|
30
|
-
|
|
22
|
+
If you run `minicor-mcp-setup`, it writes this config automatically.
|
|
31
23
|
|
|
32
|
-
|
|
24
|
+
### Claude Code
|
|
33
25
|
|
|
34
26
|
```bash
|
|
35
27
|
npm install -g @minicor/mcp-server
|
|
36
28
|
minicor-mcp-setup
|
|
29
|
+
claude mcp add minicor -- minicor-mcp
|
|
37
30
|
```
|
|
38
31
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
For headless environments:
|
|
32
|
+
### Authenticate
|
|
42
33
|
|
|
43
34
|
```bash
|
|
44
|
-
minicor
|
|
35
|
+
npx @minicor/mcp-server-setup
|
|
45
36
|
```
|
|
46
37
|
|
|
47
|
-
|
|
38
|
+
Opens a browser to sign in or create an account. Tokens are stored at `~/.minicor/tokens.json` and auto-refresh. For headless environments: `npx @minicor/mcp-server-setup --cli`.
|
|
48
39
|
|
|
49
|
-
|
|
40
|
+
### Update
|
|
50
41
|
|
|
51
|
-
|
|
52
|
-
- **Workflows** -- create, update, clone, delete, restore workflows
|
|
53
|
-
- **Flows (Steps)** -- read/write individual steps, view version history, bulk update
|
|
54
|
-
- **Executions** -- list, search, filter by date/status, inspect full payloads, view per-step results
|
|
55
|
-
- **Execute** -- trigger workflows synchronously or async, poll for status
|
|
56
|
-
- **Configuration Stores** -- manage key-value configs used by `{{config.xxx}}` references
|
|
57
|
-
- **Issues** -- create and manage workspace issues
|
|
58
|
-
- **Desktop RPA** -- connect to a VM, take screenshots, inspect UI, run scripts, build RPA workflows iteratively
|
|
59
|
-
- **Browser RPA** -- session-based browser automation with natural-language actions
|
|
60
|
-
- **RPA Debugging** -- before/after screenshots, batch testing, state reset, execution diagnosis
|
|
61
|
-
- **Log Search** _(advanced)_ -- full-text Elasticsearch search across execution logs
|
|
62
|
-
- **Incident Investigation** -- correlate failures across workflows with timeline analysis
|
|
63
|
-
- **CRON Management** _(advanced)_ -- scheduled job management
|
|
64
|
-
- **Workflow File Sync** -- pull/push workflows to local files for git version control
|
|
42
|
+
`npx` users: just restart your editor (fetches latest automatically). Global install: `npm update -g @minicor/mcp-server`.
|
|
65
43
|
|
|
66
|
-
##
|
|
44
|
+
## How It Works
|
|
67
45
|
|
|
68
|
-
Connect to a VM running the Laminar Desktop Service (LDS)
|
|
46
|
+
1. **Connect to a VM** — The agent connects to your Windows VM running the Laminar Desktop Service (LDS) via a Cloudflare tunnel
|
|
47
|
+
2. **Build automations** — The agent takes screenshots, inspects UI elements, writes Python scripts, and tests them on the VM in real time
|
|
48
|
+
3. **Deploy as workflows** — Validated scripts are saved as step-based Minicor workflows with credentials stored in config stores
|
|
49
|
+
4. **Debug failures** — Before/after screenshots, video recordings of each step, execution diagnosis, and replay on live VMs
|
|
69
50
|
|
|
70
|
-
|
|
51
|
+
## What You Can Automate
|
|
71
52
|
|
|
72
|
-
|
|
53
|
+
- **Web portals** — Chrome on the VM controlled via CDP (Chrome DevTools Protocol). Works on React/Angular SPAs, anti-bot protected sites, payor portals, EHR web apps.
|
|
54
|
+
- **Desktop applications** — Native Windows apps driven via uiautomation, pywinauto, or pyautogui. EMR clients, billing software, legacy systems.
|
|
55
|
+
- **APIs** — Direct HTTP calls when the agent discovers usable REST endpoints behind a web portal.
|
|
56
|
+
- **Hybrid workflows** — Mix browser, desktop, and API steps in a single workflow.
|
|
73
57
|
|
|
74
|
-
|
|
75
|
-
2. Start it on port 1016
|
|
76
|
-
3. Expose via Cloudflare Tunnel: `cloudflared tunnel --url http://localhost:1016`
|
|
77
|
-
4. In Cursor: `vm_connect` with the tunnel URL
|
|
78
|
-
|
|
79
|
-
### RPA Tools
|
|
80
|
-
|
|
81
|
-
| Tool | Description |
|
|
82
|
-
|---|---|
|
|
83
|
-
| `vm_connect` | Connect to LDS via Cloudflare Tunnel URL |
|
|
84
|
-
| `vm_screenshot` | Capture VM desktop screenshot |
|
|
85
|
-
| `vm_inspect_ui` | Inspect UI elements (accessibility tree, element at point, etc.) |
|
|
86
|
-
| `vm_execute_script` | Run Python script on the VM |
|
|
87
|
-
| `create_rpa_flow` | Save validated Python script as an RPA step (auto-wraps in correct format) |
|
|
88
|
-
| `vm_read_clipboard` | Read clipboard text after copy operations |
|
|
89
|
-
| `vm_screenshot_region` | Crop and zoom a specific screen region |
|
|
90
|
-
| `debug_rpa_step` | Run script with before/after screenshots |
|
|
91
|
-
| `vm_reset_state` | Smart Launch -- reset app to known state |
|
|
92
|
-
| `batch_test_rpa` | Run workflow with multiple test inputs |
|
|
93
|
-
| `get_lds_setup_guide` | Full LDS installation walkthrough |
|
|
94
|
-
|
|
95
|
-
## Browser RPA
|
|
96
|
-
|
|
97
|
-
Session-based browser automation for web applications.
|
|
98
|
-
|
|
99
|
-
| Tool | Description |
|
|
100
|
-
|---|---|
|
|
101
|
-
| `browser_connect` | Connect to browser RPA service |
|
|
102
|
-
| `browser_create_session` | Start a new browser session |
|
|
103
|
-
| `browser_act` | Natural-language browser actions |
|
|
104
|
-
| `browser_extract` | Extract data from the current page |
|
|
105
|
-
| `browser_screenshot` | Screenshot the browser |
|
|
106
|
-
| `browser_close_session` | Clean up session |
|
|
107
|
-
| `create_browser_rpa_flow` | Save browser action as a workflow step |
|
|
108
|
-
|
|
109
|
-
## Workflow Tools
|
|
110
|
-
|
|
111
|
-
| Tool | Description |
|
|
112
|
-
|---|---|
|
|
113
|
-
| `preview_flow_changes` | Diff current vs proposed code before pushing |
|
|
114
|
-
| `get_workflow_overview` | Full workflow snapshot with code + executions |
|
|
115
|
-
| `test_workflow_step` | Run up to / from / only a specific step |
|
|
116
|
-
| `diagnose_execution` | Find failures with RPA-specific error analysis |
|
|
117
|
-
| `compare_flow_versions` | Diff between two step versions |
|
|
118
|
-
|
|
119
|
-
## Advanced Setup
|
|
120
|
-
|
|
121
|
-
Elasticsearch (log search) and CRON (scheduling) are configured during `minicor-mcp-setup` under Advanced Settings, or via `~/.minicor/config.json`:
|
|
58
|
+
## VM Setup
|
|
122
59
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
60
|
+
1. Install LDS on a Windows 10/11 or Server VM with Python 3.11+
|
|
61
|
+
2. Start LDS on port 1016
|
|
62
|
+
3. Expose via Cloudflare Tunnel: `cloudflared tunnel --url http://localhost:1016`
|
|
63
|
+
4. For browser automation: start Chrome with `--remote-debugging-port=9222`
|
|
64
|
+
5. In your editor, tell the agent: "Connect to my VM at https://your-tunnel-url.com"
|
|
65
|
+
|
|
66
|
+
Use the `get_lds_setup_guide` tool for detailed step-by-step instructions.
|
|
67
|
+
|
|
68
|
+
## Tools
|
|
69
|
+
|
|
70
|
+
### VM Tools
|
|
71
|
+
|
|
72
|
+
| Tool | What it does |
|
|
73
|
+
| ---------------------- | ------------------------------------------------------------------------------------------ |
|
|
74
|
+
| `vm_connect` | Connect to VM via LDS Cloudflare tunnel URL |
|
|
75
|
+
| `vm_disconnect` | Disconnect from VM |
|
|
76
|
+
| `vm_status` | Health check for the connected VM |
|
|
77
|
+
| `vm_screenshot` | Full-screen capture of the VM desktop |
|
|
78
|
+
| `vm_screenshot_region` | Zoom into a specific screen area |
|
|
79
|
+
| `vm_execute_script` | Run a Python script on the VM |
|
|
80
|
+
| `vm_execution_status` | Check status of a running script |
|
|
81
|
+
| `vm_execution_control` | Pause, resume, stop, or skip a running script |
|
|
82
|
+
| `vm_inspect_ui` | Inspect Windows UI elements (window list, element tree, element at point, focused element) |
|
|
83
|
+
| `vm_read_clipboard` | Read VM clipboard contents |
|
|
84
|
+
|
|
85
|
+
### Automation Building
|
|
86
|
+
|
|
87
|
+
| Tool | What it does |
|
|
88
|
+
| ----------------------- | --------------------------------------------------------- |
|
|
89
|
+
| `create_rpa_flow` | Save a validated Python script as a Minicor workflow step |
|
|
90
|
+
| `debug_rpa_step` | Test a script with before/after screenshots |
|
|
91
|
+
| `vm_reset_state` | Manage windows: focus app, minimize all, close dialogs |
|
|
92
|
+
| `batch_test_rpa` | Run a workflow with multiple test inputs |
|
|
93
|
+
| `replay_execution_step` | Re-run a step from a failed execution on the VM |
|
|
94
|
+
| `get_lds_setup_guide` | LDS installation walkthrough |
|
|
95
|
+
|
|
96
|
+
### Workflows and Executions
|
|
97
|
+
|
|
98
|
+
| Tool | What it does |
|
|
99
|
+
| ---------------------------------------------------------- | --------------------------------------------------- |
|
|
100
|
+
| `create_workflow` / `update_workflow` / `delete_workflow` | Workflow CRUD |
|
|
101
|
+
| `create_flow` / `update_flow` / `delete_flow` | Step CRUD |
|
|
102
|
+
| `execute_workflow` / `execute_workflow_async` | Run workflows synchronously or async |
|
|
103
|
+
| `list_executions` / `get_execution` / `get_full_execution` | View execution history and details |
|
|
104
|
+
| `diagnose_execution` | Analyze failures with RPA-specific pattern matching |
|
|
105
|
+
| `get_workflow_overview` | Full snapshot: steps, code, recent executions |
|
|
106
|
+
| `test_workflow_step` | Run a single step in isolation |
|
|
107
|
+
| `preview_flow_changes` / `compare_flow_versions` | Diff code before deploying |
|
|
108
|
+
|
|
109
|
+
### 2FA / OTP
|
|
110
|
+
|
|
111
|
+
| Tool | What it does |
|
|
112
|
+
| --------------------- | -------------------------------------------------------------------- |
|
|
113
|
+
| `tfa_provision_phone` | Buy a Twilio phone number for the workspace (SMS webhook auto-configured) |
|
|
114
|
+
| `tfa_provision_email` | Generate a Mailgun email address for OTP capture |
|
|
115
|
+
| `tfa_list_channels` | List all provisioned phone numbers and email addresses |
|
|
116
|
+
| `tfa_delete_channel` | Remove a channel (releases Twilio number if phone) |
|
|
117
|
+
| `tfa_register_secret` | Store a TOTP secret (base32 or otpauth:// URI), encrypted at rest |
|
|
118
|
+
| `tfa_list_secrets` | List stored TOTP secrets for the workspace |
|
|
119
|
+
| `tfa_delete_secret` | Remove a stored TOTP secret |
|
|
120
|
+
| `tfa_generate_totp` | Get the current 6-digit TOTP code + seconds until expiry |
|
|
121
|
+
| `tfa_verify_totp` | Verify a TOTP code against a stored secret |
|
|
122
|
+
| `tfa_parse_qr` | Parse a QR code image to extract TOTP parameters |
|
|
123
|
+
| `tfa_request_sms_otp` | Wait for an SMS OTP (blocks until SMS arrives or timeout) |
|
|
124
|
+
| `tfa_request_email_otp` | Wait for an email OTP (blocks until email arrives or timeout) |
|
|
125
|
+
| `tfa_get_challenge` | Check the status of a pending challenge |
|
|
126
|
+
| `tfa_resolve_challenge` | Manually resolve a challenge (for captchas or Slack-provided codes)|
|
|
127
|
+
| `tfa_cancel_challenge`| Cancel a pending challenge |
|
|
128
|
+
|
|
129
|
+
Workflows that encounter 2FA prompts use these tools to auto-resolve OTP challenges. Provision channels once per workspace, store the IDs in config stores, and reference them with `{{config.sms_channel_id}}` or `{{config.totp_secret_id}}` at runtime.
|
|
130
|
+
|
|
131
|
+
### Config Stores
|
|
132
|
+
|
|
133
|
+
| Tool | What it does |
|
|
134
|
+
| --------------------------------------------------- | --------------------------------------------------- |
|
|
135
|
+
| `create_config_store` | Create a credential store with key-value properties |
|
|
136
|
+
| `list_config_stores` / `get_config_store` | Browse stores |
|
|
137
|
+
| `update_config_property` / `remove_config_property` | Manage individual properties |
|
|
138
|
+
|
|
139
|
+
Scripts reference credentials as `{{config.propertyKey}}` — resolved at runtime by the workflow engine.
|
|
140
|
+
|
|
141
|
+
### Workflow Sync
|
|
142
|
+
|
|
143
|
+
| Tool | What it does |
|
|
144
|
+
| --------------------------------- | --------------------------------------------- |
|
|
145
|
+
| `init_project` | Scaffold a git-ready project from a workspace |
|
|
146
|
+
| `pull_workflow` / `push_workflow` | Sync individual workflows |
|
|
147
|
+
| `pull_all` / `push_changed` | Bulk sync |
|
|
148
|
+
| `sync_status` | Diff local vs deployed |
|
|
149
|
+
|
|
150
|
+
## Prompts
|
|
151
|
+
|
|
152
|
+
| Prompt | What it does |
|
|
153
|
+
| -------------------------- | ------------------------------------------------------------------------------------------------- |
|
|
154
|
+
| `build-rpa-workflow` | Guided automation building: strategy selection, iterative build loop, Minicor testing, deployment |
|
|
155
|
+
| `debug-workflow-execution` | Analyze a failed execution with VM-aware debugging and replay |
|
|
156
|
+
| `minicor-workflow-guide` | Full workflow specification: step types, data access, keywords, libraries |
|
|
157
|
+
| `2fa-workflow-guide` | Handling 2FA in workflows: provisioning channels, TOTP secrets, auto-resolving OTP codes |
|
|
134
158
|
|
|
135
159
|
## Auth
|
|
136
160
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
- **
|
|
140
|
-
- **
|
|
141
|
-
- **Auto-refresh**: tokens refresh automatically before expiry
|
|
142
|
-
- **Region**: US (default) or Canada, selected during setup
|
|
161
|
+
- **Sign in**: `minicor-mcp-setup` (browser) or `minicor-mcp-setup --cli`
|
|
162
|
+
- **Token storage**: `~/.minicor/tokens.json`
|
|
163
|
+
- **Auto-refresh**: tokens refresh before expiry
|
|
164
|
+
- **Regions**: US (default) or Canada
|
|
143
165
|
|
|
144
166
|
## Development
|
|
145
167
|
|
|
@@ -147,25 +169,4 @@ Authentication uses stored session tokens only. No API keys.
|
|
|
147
169
|
npm install
|
|
148
170
|
npm run build
|
|
149
171
|
npm test
|
|
150
|
-
npm run dev # watch mode
|
|
151
|
-
npm run test:watch # test watch mode
|
|
152
172
|
```
|
|
153
|
-
|
|
154
|
-
### Project Structure
|
|
155
|
-
|
|
156
|
-
```
|
|
157
|
-
src/
|
|
158
|
-
index.ts -- server orchestrator (auth, init, registration)
|
|
159
|
-
helpers.ts -- shared response helpers (ok, text, safe, buildRpaProgram)
|
|
160
|
-
state.ts -- session state (VM + browser connections)
|
|
161
|
-
paths.ts -- token/config path resolution with ~/.minicor/ + ~/.laminar/ fallback
|
|
162
|
-
types.ts -- shared ToolDeps interface
|
|
163
|
-
setup.ts -- interactive setup (browser + CLI modes)
|
|
164
|
-
tools/ -- tool modules (each exports register())
|
|
165
|
-
prompts/ -- prompt modules (each exports register())
|
|
166
|
-
__tests__/ -- vitest unit tests
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
## License
|
|
170
|
-
|
|
171
|
-
MIT
|
|
@@ -40,9 +40,9 @@ describe("buildRpaProgram", () => {
|
|
|
40
40
|
it("generates cloudflare_tunnel pattern with lam.httpRequest", () => {
|
|
41
41
|
const program = buildRpaProgram('print("hello")', "cloudflare_tunnel", "test-step", "Test Step", "A test step");
|
|
42
42
|
expect(program).toContain("lam.httpRequest");
|
|
43
|
-
expect(program).toContain("{{config.
|
|
44
|
-
expect(program).toContain("{{config.
|
|
45
|
-
expect(program).toContain("{{config.
|
|
43
|
+
expect(program).toContain("{{config.minicor_desktop_service_url}}/execute");
|
|
44
|
+
expect(program).toContain("{{config.minicor_desktop_service_api_key}}");
|
|
45
|
+
expect(program).toContain("{{config.minicor_desktop_service_id}}");
|
|
46
46
|
expect(program).toContain('"flowId": "test-step"');
|
|
47
47
|
expect(program).toContain('print("hello")');
|
|
48
48
|
expect(program).toMatch(/^\(data\) =>/);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tfa-client.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/tfa-client.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import * as tfa from "../tfa-client.js";
|
|
3
|
+
describe("getTfaBaseUrl", () => {
|
|
4
|
+
const original = process.env.TFA_OTP_URL;
|
|
5
|
+
afterEach(() => {
|
|
6
|
+
if (original !== undefined) {
|
|
7
|
+
process.env.TFA_OTP_URL = original;
|
|
8
|
+
}
|
|
9
|
+
else {
|
|
10
|
+
delete process.env.TFA_OTP_URL;
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
it("returns default when env var is unset", () => {
|
|
14
|
+
delete process.env.TFA_OTP_URL;
|
|
15
|
+
expect(tfa.getTfaBaseUrl()).toBe("https://tfa-otp.minicor.com");
|
|
16
|
+
});
|
|
17
|
+
it("returns env var when set", () => {
|
|
18
|
+
process.env.TFA_OTP_URL = "http://localhost:3847";
|
|
19
|
+
expect(tfa.getTfaBaseUrl()).toBe("http://localhost:3847");
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
describe("tfa-client HTTP calls", () => {
|
|
23
|
+
const BASE = "https://tfa-otp.minicor.com";
|
|
24
|
+
const API_KEY = "test-key-123";
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
vi.stubGlobal("fetch", vi.fn());
|
|
27
|
+
});
|
|
28
|
+
afterEach(() => {
|
|
29
|
+
vi.restoreAllMocks();
|
|
30
|
+
});
|
|
31
|
+
function mockFetch(status, body) {
|
|
32
|
+
fetch.mockResolvedValue({
|
|
33
|
+
ok: status >= 200 && status < 300,
|
|
34
|
+
status,
|
|
35
|
+
json: () => Promise.resolve(body),
|
|
36
|
+
text: () => Promise.resolve(JSON.stringify(body)),
|
|
37
|
+
headers: new Headers({ "content-type": "application/json" }),
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
function lastFetchCall() {
|
|
41
|
+
const calls = fetch.mock.calls;
|
|
42
|
+
return { url: calls[0][0], init: calls[0][1] };
|
|
43
|
+
}
|
|
44
|
+
// ── Channels ──
|
|
45
|
+
it("provisionPhone calls POST /channels/phone with API key header", async () => {
|
|
46
|
+
mockFetch(201, { id: "ch_123", address: "+15551234567" });
|
|
47
|
+
const result = await tfa.provisionPhone(BASE, API_KEY);
|
|
48
|
+
const { url, init } = lastFetchCall();
|
|
49
|
+
expect(url).toBe(`${BASE}/channels/phone`);
|
|
50
|
+
expect(init.method).toBe("POST");
|
|
51
|
+
expect(init.headers["X-API-Key"]).toBe(API_KEY);
|
|
52
|
+
expect(result).toEqual({ id: "ch_123", address: "+15551234567" });
|
|
53
|
+
});
|
|
54
|
+
it("provisionEmail calls POST /channels/email", async () => {
|
|
55
|
+
mockFetch(201, { id: "ch_456", address: "rpa-abc@otp.minicor.com" });
|
|
56
|
+
await tfa.provisionEmail(BASE, API_KEY);
|
|
57
|
+
const { url, init } = lastFetchCall();
|
|
58
|
+
expect(url).toBe(`${BASE}/channels/email`);
|
|
59
|
+
expect(init.method).toBe("POST");
|
|
60
|
+
});
|
|
61
|
+
it("listChannels calls GET /channels with optional type filter", async () => {
|
|
62
|
+
mockFetch(200, []);
|
|
63
|
+
await tfa.listChannels(BASE, API_KEY, { type: "phone" });
|
|
64
|
+
const { url } = lastFetchCall();
|
|
65
|
+
expect(url).toBe(`${BASE}/channels?type=phone`);
|
|
66
|
+
});
|
|
67
|
+
it("listChannels omits query string when no filter", async () => {
|
|
68
|
+
mockFetch(200, []);
|
|
69
|
+
await tfa.listChannels(BASE, API_KEY);
|
|
70
|
+
const { url } = lastFetchCall();
|
|
71
|
+
expect(url).toBe(`${BASE}/channels`);
|
|
72
|
+
});
|
|
73
|
+
it("deleteChannel calls DELETE /channels/:id", async () => {
|
|
74
|
+
fetch.mockResolvedValue({
|
|
75
|
+
ok: true,
|
|
76
|
+
status: 204,
|
|
77
|
+
json: () => Promise.resolve(undefined),
|
|
78
|
+
text: () => Promise.resolve(""),
|
|
79
|
+
headers: new Headers(),
|
|
80
|
+
});
|
|
81
|
+
await tfa.deleteChannel(BASE, API_KEY, "ch_123");
|
|
82
|
+
const { url, init } = lastFetchCall();
|
|
83
|
+
expect(url).toBe(`${BASE}/channels/ch_123`);
|
|
84
|
+
expect(init.method).toBe("DELETE");
|
|
85
|
+
});
|
|
86
|
+
// ── Secrets ──
|
|
87
|
+
it("createSecret sends TOTP secret data", async () => {
|
|
88
|
+
mockFetch(201, { id: "sec_123" });
|
|
89
|
+
await tfa.createSecret(BASE, API_KEY, {
|
|
90
|
+
type: "totp",
|
|
91
|
+
name: "Acme",
|
|
92
|
+
value: "JBSWY3DPEHPK3PXP",
|
|
93
|
+
site: "acme.com",
|
|
94
|
+
});
|
|
95
|
+
const { url, init } = lastFetchCall();
|
|
96
|
+
expect(url).toBe(`${BASE}/secrets`);
|
|
97
|
+
expect(init.method).toBe("POST");
|
|
98
|
+
const body = JSON.parse(init.body);
|
|
99
|
+
expect(body.type).toBe("totp");
|
|
100
|
+
expect(body.name).toBe("Acme");
|
|
101
|
+
expect(body.value).toBe("JBSWY3DPEHPK3PXP");
|
|
102
|
+
});
|
|
103
|
+
it("createSecret supports otpauth URI", async () => {
|
|
104
|
+
mockFetch(201, { id: "sec_456" });
|
|
105
|
+
await tfa.createSecret(BASE, API_KEY, {
|
|
106
|
+
type: "totp",
|
|
107
|
+
name: "Acme",
|
|
108
|
+
uri: "otpauth://totp/Acme:user@example.com?secret=JBSWY3DPEHPK3PXP",
|
|
109
|
+
});
|
|
110
|
+
const body = JSON.parse(lastFetchCall().init.body);
|
|
111
|
+
expect(body.uri).toContain("otpauth://");
|
|
112
|
+
expect(body.value).toBeUndefined();
|
|
113
|
+
});
|
|
114
|
+
it("listSecrets with site filter", async () => {
|
|
115
|
+
mockFetch(200, []);
|
|
116
|
+
await tfa.listSecrets(BASE, API_KEY, { site: "acme.com" });
|
|
117
|
+
expect(lastFetchCall().url).toBe(`${BASE}/secrets?site=acme.com`);
|
|
118
|
+
});
|
|
119
|
+
it("generateTotp calls POST /secrets/:id/generate", async () => {
|
|
120
|
+
mockFetch(200, { code: "482901", expiresIn: 14 });
|
|
121
|
+
const result = await tfa.generateTotp(BASE, API_KEY, "sec_123");
|
|
122
|
+
expect(lastFetchCall().url).toBe(`${BASE}/secrets/sec_123/generate`);
|
|
123
|
+
expect(result).toEqual({ code: "482901", expiresIn: 14 });
|
|
124
|
+
});
|
|
125
|
+
it("verifyTotp sends code in body", async () => {
|
|
126
|
+
mockFetch(200, { valid: true, delta: 0 });
|
|
127
|
+
await tfa.verifyTotp(BASE, API_KEY, "sec_123", "482901");
|
|
128
|
+
const { url, init } = lastFetchCall();
|
|
129
|
+
expect(url).toBe(`${BASE}/secrets/sec_123/verify`);
|
|
130
|
+
expect(JSON.parse(init.body)).toEqual({ code: "482901" });
|
|
131
|
+
});
|
|
132
|
+
it("parseQr sends imageUrl", async () => {
|
|
133
|
+
mockFetch(200, { uri: "otpauth://totp/...", secretPreview: "****PXP" });
|
|
134
|
+
await tfa.parseQr(BASE, API_KEY, { imageUrl: "https://example.com/qr.png" });
|
|
135
|
+
const body = JSON.parse(lastFetchCall().init.body);
|
|
136
|
+
expect(body.imageUrl).toBe("https://example.com/qr.png");
|
|
137
|
+
});
|
|
138
|
+
// ── Challenges ──
|
|
139
|
+
it("requestSmsOtp sends channelId and timeout", async () => {
|
|
140
|
+
mockFetch(200, { status: "resolved", resolution: { value: "123456" } });
|
|
141
|
+
await tfa.requestSmsOtp(BASE, API_KEY, {
|
|
142
|
+
channelId: "ch_phone1",
|
|
143
|
+
timeoutSeconds: 60,
|
|
144
|
+
});
|
|
145
|
+
const body = JSON.parse(lastFetchCall().init.body);
|
|
146
|
+
expect(body.channelId).toBe("ch_phone1");
|
|
147
|
+
expect(body.timeoutSeconds).toBe(60);
|
|
148
|
+
});
|
|
149
|
+
it("requestEmailOtp hits /challenges/email-otp", async () => {
|
|
150
|
+
mockFetch(200, { status: "resolved" });
|
|
151
|
+
await tfa.requestEmailOtp(BASE, API_KEY, { channelId: "ch_email1" });
|
|
152
|
+
expect(lastFetchCall().url).toBe(`${BASE}/challenges/email-otp`);
|
|
153
|
+
});
|
|
154
|
+
it("getChallenge hits GET /challenges/:id", async () => {
|
|
155
|
+
mockFetch(200, { id: "chal_1", status: "pending" });
|
|
156
|
+
await tfa.getChallenge(BASE, API_KEY, "chal_1");
|
|
157
|
+
expect(lastFetchCall().url).toBe(`${BASE}/challenges/chal_1`);
|
|
158
|
+
});
|
|
159
|
+
it("resolveChallenge sends value and resolvedBy", async () => {
|
|
160
|
+
mockFetch(200, { status: "resolved" });
|
|
161
|
+
await tfa.resolveChallenge(BASE, API_KEY, "chal_1", {
|
|
162
|
+
value: "xK9mP2",
|
|
163
|
+
resolvedBy: "manual",
|
|
164
|
+
});
|
|
165
|
+
const { url, init } = lastFetchCall();
|
|
166
|
+
expect(url).toBe(`${BASE}/challenges/chal_1/resolve`);
|
|
167
|
+
expect(JSON.parse(init.body)).toEqual({
|
|
168
|
+
value: "xK9mP2",
|
|
169
|
+
resolvedBy: "manual",
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
it("cancelChallenge calls DELETE /challenges/:id", async () => {
|
|
173
|
+
mockFetch(200, { status: "cancelled" });
|
|
174
|
+
await tfa.cancelChallenge(BASE, API_KEY, "chal_1");
|
|
175
|
+
const { url, init } = lastFetchCall();
|
|
176
|
+
expect(url).toBe(`${BASE}/challenges/chal_1`);
|
|
177
|
+
expect(init.method).toBe("DELETE");
|
|
178
|
+
});
|
|
179
|
+
// ── Error handling ──
|
|
180
|
+
it("throws on non-ok response with status and body", async () => {
|
|
181
|
+
mockFetch(404, { error: "Not found" });
|
|
182
|
+
await expect(tfa.getChallenge(BASE, API_KEY, "bad_id")).rejects.toThrow(/2FA API.*404/);
|
|
183
|
+
});
|
|
184
|
+
// ── URL normalization ──
|
|
185
|
+
it("strips trailing slashes from base URL", async () => {
|
|
186
|
+
mockFetch(200, []);
|
|
187
|
+
await tfa.listChannels("https://example.com///", API_KEY);
|
|
188
|
+
expect(lastFetchCall().url).toBe("https://example.com/channels");
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
//# sourceMappingURL=tfa-client.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tfa-client.test.js","sourceRoot":"","sources":["../../src/__tests__/tfa-client.test.ts"],"names":[],"mappings":"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"}
|
package/dist/helpers.d.ts
CHANGED
|
@@ -20,6 +20,6 @@ export declare function safe<T>(fn: () => Promise<T>): Promise<{
|
|
|
20
20
|
text: string;
|
|
21
21
|
}[];
|
|
22
22
|
}>;
|
|
23
|
-
export declare const NOT_CONNECTED_VM = "No VM connected. Ask the user for their Cloudflare Tunnel URL for the
|
|
24
|
-
export declare function buildRpaProgram(pythonScript: string, pattern: "cloudflare_tunnel" | "channel", flowId: string, stepName: string, stepDescription: string): string;
|
|
23
|
+
export declare const NOT_CONNECTED_VM = "No VM connected. Ask the user for their Cloudflare Tunnel URL for the Minicor Desktop Service, then call vm_connect.";
|
|
24
|
+
export declare function buildRpaProgram(pythonScript: string, pattern: "cloudflare_tunnel" | "channel" | "browser", flowId: string, stepName: string, stepDescription: string): string;
|
|
25
25
|
//# sourceMappingURL=helpers.d.ts.map
|
package/dist/helpers.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../src/helpers.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,wBAAgB,IAAI,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAE1C;AAED,wBAAgB,EAAE,CAAC,IAAI,EAAE,OAAO;;;;;EAE/B;AAED,wBAAgB,IAAI,CAAC,GAAG,EAAE,MAAM;;;;;EAE/B;AAED,wBAAsB,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC;;;;;GAMjD;AAED,eAAO,MAAM,gBAAgB,yHAAyH,CAAC;AAEvJ,wBAAgB,eAAe,CAC7B,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,mBAAmB,GAAG,SAAS,
|
|
1
|
+
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../src/helpers.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,wBAAgB,IAAI,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAE1C;AAED,wBAAgB,EAAE,CAAC,IAAI,EAAE,OAAO;;;;;EAE/B;AAED,wBAAgB,IAAI,CAAC,GAAG,EAAE,MAAM;;;;;EAE/B;AAED,wBAAsB,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC;;;;;GAMjD;AAED,eAAO,MAAM,gBAAgB,yHAAyH,CAAC;AAEvJ,wBAAgB,eAAe,CAC7B,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,mBAAmB,GAAG,SAAS,GAAG,SAAS,EACpD,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,eAAe,EAAE,MAAM,GACtB,MAAM,CAwCR"}
|
package/dist/helpers.js
CHANGED
|
@@ -18,7 +18,7 @@ export async function safe(fn) {
|
|
|
18
18
|
return text(`Error: ${e.message}`);
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
|
-
export const NOT_CONNECTED_VM = `No VM connected. Ask the user for their Cloudflare Tunnel URL for the
|
|
21
|
+
export const NOT_CONNECTED_VM = `No VM connected. Ask the user for their Cloudflare Tunnel URL for the Minicor Desktop Service, then call vm_connect.`;
|
|
22
22
|
export function buildRpaProgram(pythonScript, pattern, flowId, stepName, stepDescription) {
|
|
23
23
|
const escaped = pythonScript.replace(/`/g, "\\`").replace(/\$/g, "\\$");
|
|
24
24
|
if (pattern === "channel") {
|
|
@@ -34,6 +34,9 @@ ${escaped}
|
|
|
34
34
|
};
|
|
35
35
|
}`;
|
|
36
36
|
}
|
|
37
|
+
// Browser scripts go to /execute/browser — MDS provides an isolated Chrome tab
|
|
38
|
+
// Desktop scripts go to /execute — no Chrome, just runs the script
|
|
39
|
+
const endpoint = pattern === "browser" ? "/execute/browser" : "/execute";
|
|
37
40
|
return `(data) => {
|
|
38
41
|
const pythonScript = \`
|
|
39
42
|
${escaped}
|
|
@@ -41,11 +44,11 @@ ${escaped}
|
|
|
41
44
|
return {
|
|
42
45
|
"lam.httpRequest": {
|
|
43
46
|
"method": "POST",
|
|
44
|
-
"url": "{{config.
|
|
47
|
+
"url": "{{config.minicor_desktop_service_url}}${endpoint}",
|
|
45
48
|
"headers": {
|
|
46
49
|
"Content-Type": "application/json",
|
|
47
|
-
"X-API-Key": "{{config.
|
|
48
|
-
"X-Service-ID": "{{config.
|
|
50
|
+
"X-API-Key": "{{config.minicor_desktop_service_api_key}}",
|
|
51
|
+
"X-Service-ID": "{{config.minicor_desktop_service_id}}"
|
|
49
52
|
},
|
|
50
53
|
"body": {
|
|
51
54
|
"flowId": "${flowId}",
|
package/dist/helpers.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.js","sourceRoot":"","sources":["../src/helpers.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,UAAU,IAAI,CAAC,IAAa;IAChC,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AACvC,CAAC;AAED,MAAM,UAAU,EAAE,CAAC,IAAa;IAC9B,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;AACpE,CAAC;AAED,MAAM,UAAU,IAAI,CAAC,GAAW;IAC9B,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC;AAC7D,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,IAAI,CAAI,EAAoB;IAChD,IAAI,CAAC;QACH,OAAO,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACxB,CAAC;IAAC,OAAO,CAAM,EAAE,CAAC;QAChB,OAAO,IAAI,CAAC,UAAU,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;IACrC,CAAC;AACH,CAAC;AAED,MAAM,CAAC,MAAM,gBAAgB,GAAG,sHAAsH,CAAC;AAEvJ,MAAM,UAAU,eAAe,CAC7B,YAAoB,EACpB,
|
|
1
|
+
{"version":3,"file":"helpers.js","sourceRoot":"","sources":["../src/helpers.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,UAAU,IAAI,CAAC,IAAa;IAChC,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AACvC,CAAC;AAED,MAAM,UAAU,EAAE,CAAC,IAAa;IAC9B,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;AACpE,CAAC;AAED,MAAM,UAAU,IAAI,CAAC,GAAW;IAC9B,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC;AAC7D,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,IAAI,CAAI,EAAoB;IAChD,IAAI,CAAC;QACH,OAAO,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACxB,CAAC;IAAC,OAAO,CAAM,EAAE,CAAC;QAChB,OAAO,IAAI,CAAC,UAAU,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;IACrC,CAAC;AACH,CAAC;AAED,MAAM,CAAC,MAAM,gBAAgB,GAAG,sHAAsH,CAAC;AAEvJ,MAAM,UAAU,eAAe,CAC7B,YAAoB,EACpB,OAAoD,EACpD,MAAc,EACd,QAAgB,EAChB,eAAuB;IAEvB,MAAM,OAAO,GAAG,YAAY,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IACxE,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC1B,OAAO;;EAET,OAAO;;;;;;;;EAQP,CAAC;IACD,CAAC;IACD,+EAA+E;IAC/E,mEAAmE;IACnE,MAAM,QAAQ,GAAG,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,UAAU,CAAC;IACzE,OAAO;;EAEP,OAAO;;;;;sDAK6C,QAAQ;;;;;;;qBAOzC,MAAM;;;2BAGA,MAAM,eAAe,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,sBAAsB,eAAe,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC;;;;EAIrI,CAAC;AACH,CAAC"}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA;;;;;GAKG;AA4DH,wBAAgB,UAAU,IAAI,MAAM,CAKnC"}
|
package/dist/index.js
CHANGED
|
@@ -17,9 +17,11 @@ import { register as registerConfigStores } from "./tools/config-stores.js";
|
|
|
17
17
|
import { register as registerSyncTools } from "./tools/sync-tools.js";
|
|
18
18
|
import { register as registerVm } from "./tools/vm.js";
|
|
19
19
|
import { register as registerVmRpa } from "./tools/vm-rpa.js";
|
|
20
|
+
import { register as registerTfa } from "./tools/tfa.js";
|
|
20
21
|
import { register as registerWorkflowGuide } from "./prompts/workflow-guide.js";
|
|
21
22
|
import { register as registerDebugExecution } from "./prompts/debug-execution.js";
|
|
22
23
|
import { register as registerBuildRpa } from "./prompts/build-rpa.js";
|
|
24
|
+
import { register as registerTfaGuide } from "./prompts/tfa-guide.js";
|
|
23
25
|
// ─── Token management ────────────────────────────────────────
|
|
24
26
|
const REFRESH_BUFFER_MS = 5 * 60 * 1000;
|
|
25
27
|
function readStoredTokens() {
|
|
@@ -152,9 +154,11 @@ function registerAll() {
|
|
|
152
154
|
registerSyncTools(deps);
|
|
153
155
|
registerVm(deps);
|
|
154
156
|
registerVmRpa(deps);
|
|
157
|
+
registerTfa(deps);
|
|
155
158
|
registerWorkflowGuide(deps);
|
|
156
159
|
registerDebugExecution(deps);
|
|
157
160
|
registerBuildRpa(deps);
|
|
161
|
+
registerTfaGuide(deps);
|
|
158
162
|
}
|
|
159
163
|
// ─── Main ────────────────────────────────────────────────────
|
|
160
164
|
async function main() {
|