roku-mcp 1.1.1 → 1.2.1
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 +89 -21
- package/dist/discovery.d.ts +9 -0
- package/dist/discovery.d.ts.map +1 -0
- package/dist/discovery.js +79 -0
- package/dist/discovery.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/roku-config.d.ts +3 -2
- package/dist/roku-config.d.ts.map +1 -1
- package/dist/roku-config.js +30 -7
- package/dist/roku-config.js.map +1 -1
- package/dist/tools/console.d.ts.map +1 -1
- package/dist/tools/console.js +6 -8
- package/dist/tools/console.js.map +1 -1
- package/dist/tools/deploy.d.ts.map +1 -1
- package/dist/tools/deploy.js +24 -12
- package/dist/tools/deploy.js.map +1 -1
- package/dist/tools/ecp.d.ts.map +1 -1
- package/dist/tools/ecp.js +41 -22
- package/dist/tools/ecp.js.map +1 -1
- package/dist/tools/screenshot.d.ts.map +1 -1
- package/dist/tools/screenshot.js +3 -4
- package/dist/tools/screenshot.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,15 +8,17 @@ A [Model Context Protocol](https://modelcontextprotocol.io/) (MCP) server for Ro
|
|
|
8
8
|
npm install -g roku-mcp
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
Or run directly with npx:
|
|
11
|
+
Or run directly with npx (no install required):
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
14
|
npx roku-mcp
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
##
|
|
17
|
+
## Client Configuration
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
### Cursor
|
|
20
|
+
|
|
21
|
+
1. Create `.cursor/mcp.json` in your project root:
|
|
20
22
|
|
|
21
23
|
```json
|
|
22
24
|
{
|
|
@@ -33,38 +35,59 @@ Add the following to your project's `.cursor/mcp.json`:
|
|
|
33
35
|
}
|
|
34
36
|
```
|
|
35
37
|
|
|
36
|
-
|
|
38
|
+
2. Reload the window: `Cmd+Shift+P` (macOS) / `Ctrl+Shift+P` (Windows/Linux) → **Developer: Reload Window**
|
|
39
|
+
3. Go to **Cursor Settings → MCP** and verify the "roku" server shows a green status. If it appears disabled, click the toggle to enable it.
|
|
37
40
|
|
|
38
|
-
|
|
41
|
+
### VS Code
|
|
39
42
|
|
|
40
|
-
|
|
41
|
-
|---|---|
|
|
42
|
-
| `ROKU_DEVICE_HOST` | IP address or hostname of the Roku device |
|
|
43
|
-
| `ROKU_DEVICE_PASSWORD` | Developer password for the Roku device |
|
|
43
|
+
Requires VS Code **1.99+** with the GitHub Copilot extension.
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
1. Create `.vscode/mcp.json` in your project root:
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
```json
|
|
48
|
+
{
|
|
49
|
+
"servers": {
|
|
50
|
+
"roku": {
|
|
51
|
+
"command": "npx",
|
|
52
|
+
"args": ["-y", "roku-mcp"],
|
|
53
|
+
"env": {
|
|
54
|
+
"ROKU_DEVICE_HOST": "192.168.1.XXX",
|
|
55
|
+
"ROKU_DEVICE_PASSWORD": "your-password"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
48
61
|
|
|
49
|
-
|
|
62
|
+
2. Reload the window: `Cmd+Shift+P` / `Ctrl+Shift+P` → **Developer: Reload Window**
|
|
63
|
+
3. Open Copilot Chat and switch to **Agent mode** (select from the chat mode dropdown). The roku tools will be available there.
|
|
50
64
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
65
|
+
### Claude Desktop
|
|
66
|
+
|
|
67
|
+
Add to your Claude Desktop config file:
|
|
68
|
+
- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
69
|
+
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
55
70
|
|
|
56
71
|
```json
|
|
57
72
|
{
|
|
58
73
|
"mcpServers": {
|
|
59
74
|
"roku": {
|
|
60
75
|
"command": "npx",
|
|
61
|
-
"args": ["-y", "roku-mcp"]
|
|
76
|
+
"args": ["-y", "roku-mcp"],
|
|
77
|
+
"env": {
|
|
78
|
+
"ROKU_DEVICE_HOST": "192.168.1.XXX",
|
|
79
|
+
"ROKU_DEVICE_PASSWORD": "your-password"
|
|
80
|
+
}
|
|
62
81
|
}
|
|
63
82
|
}
|
|
64
83
|
}
|
|
65
84
|
```
|
|
66
85
|
|
|
67
|
-
|
|
86
|
+
Restart Claude Desktop after saving.
|
|
87
|
+
|
|
88
|
+
### Windsurf
|
|
89
|
+
|
|
90
|
+
Create `.windsurf/mcp.json` in your project root:
|
|
68
91
|
|
|
69
92
|
```json
|
|
70
93
|
{
|
|
@@ -73,21 +96,60 @@ If your project uses different variable names (e.g. `ROKU_IP`, `ROKU_DEV_PASSWOR
|
|
|
73
96
|
"command": "npx",
|
|
74
97
|
"args": ["-y", "roku-mcp"],
|
|
75
98
|
"env": {
|
|
76
|
-
"ROKU_DEVICE_HOST": "
|
|
77
|
-
"ROKU_DEVICE_PASSWORD": "
|
|
99
|
+
"ROKU_DEVICE_HOST": "192.168.1.XXX",
|
|
100
|
+
"ROKU_DEVICE_PASSWORD": "your-password"
|
|
78
101
|
}
|
|
79
102
|
}
|
|
80
103
|
}
|
|
81
104
|
}
|
|
82
105
|
```
|
|
83
106
|
|
|
84
|
-
|
|
107
|
+
Reload the window after saving.
|
|
108
|
+
|
|
109
|
+
### Any MCP-compatible client
|
|
110
|
+
|
|
111
|
+
The server uses **stdio transport**. Any client that supports MCP can launch it with:
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
command: npx
|
|
115
|
+
args: ["-y", "roku-mcp"]
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Pass `ROKU_DEVICE_HOST` and `ROKU_DEVICE_PASSWORD` as environment variables, or omit the host to use SSDP auto-discovery.
|
|
119
|
+
|
|
120
|
+
## Environment Variables
|
|
121
|
+
|
|
122
|
+
| Variable | Description |
|
|
123
|
+
|---|---|
|
|
124
|
+
| `ROKU_DEVICE_HOST` | IP address or hostname of the Roku device |
|
|
125
|
+
| `ROKU_DEVICE_PASSWORD` | Developer password for the Roku device |
|
|
126
|
+
|
|
127
|
+
Both can also be passed as parameters on each tool call, which override the environment variables.
|
|
128
|
+
|
|
129
|
+
### Auto-discovery
|
|
130
|
+
|
|
131
|
+
If `ROKU_DEVICE_HOST` is not set and no `host` parameter is provided, the server automatically discovers Roku devices on the local network using SSDP and uses the first one found. You can also use the `roku_discover` tool to list all available devices. Note that the password cannot be discovered and must still be configured.
|
|
132
|
+
|
|
133
|
+
### `.env` file support
|
|
134
|
+
|
|
135
|
+
The server automatically loads a `.env` file from the current working directory using [dotenv](https://www.npmjs.com/package/dotenv). If your project's `.env` already uses `ROKU_DEVICE_HOST` and `ROKU_DEVICE_PASSWORD`, the server picks them up with no extra configuration — just omit the `env` block from your MCP config:
|
|
85
136
|
|
|
86
137
|
```
|
|
87
138
|
ROKU_DEVICE_HOST=192.168.1.100
|
|
88
139
|
ROKU_DEVICE_PASSWORD=my-password
|
|
89
140
|
```
|
|
90
141
|
|
|
142
|
+
If your project uses different variable names (e.g. `ROKU_IP`, `ROKU_DEV_PASSWORD`), you can map them in the `env` block:
|
|
143
|
+
|
|
144
|
+
```json
|
|
145
|
+
"env": {
|
|
146
|
+
"ROKU_DEVICE_HOST": "${ROKU_IP}",
|
|
147
|
+
"ROKU_DEVICE_PASSWORD": "${ROKU_DEV_PASSWORD}"
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Or simply add the two expected variables to your `.env` alongside your existing ones.
|
|
152
|
+
|
|
91
153
|
## Available Tools
|
|
92
154
|
|
|
93
155
|
### Deploy
|
|
@@ -97,6 +159,12 @@ ROKU_DEVICE_PASSWORD=my-password
|
|
|
97
159
|
| `roku_deploy` | Sideload (deploy) a Roku app to the device |
|
|
98
160
|
| `roku_delete_dev_channel` | Delete the currently sideloaded developer channel |
|
|
99
161
|
|
|
162
|
+
### Discovery
|
|
163
|
+
|
|
164
|
+
| Tool | Description |
|
|
165
|
+
|---|---|
|
|
166
|
+
| `roku_discover` | Scan the local network for Roku devices via SSDP |
|
|
167
|
+
|
|
100
168
|
### ECP (External Control Protocol)
|
|
101
169
|
|
|
102
170
|
| Tool | Description |
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface DiscoveredDevice {
|
|
2
|
+
host: string;
|
|
3
|
+
location: string;
|
|
4
|
+
usn: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function discoverRokuDevices(timeoutMs?: number): Promise<DiscoveredDevice[]>;
|
|
7
|
+
export declare function discoverFirstHost(): Promise<string | null>;
|
|
8
|
+
export declare function clearDiscoveryCache(): void;
|
|
9
|
+
//# sourceMappingURL=discovery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../src/discovery.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;CACb;AAmCD,wBAAsB,mBAAmB,CAAC,SAAS,SAAO,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAwCvF;AAED,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAShE;AAED,wBAAgB,mBAAmB,IAAI,IAAI,CAE1C"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import dgram from 'dgram';
|
|
2
|
+
const SSDP_ADDRESS = '239.255.255.250';
|
|
3
|
+
const SSDP_PORT = 1900;
|
|
4
|
+
const SEARCH_TARGET = 'roku:ecp';
|
|
5
|
+
const M_SEARCH = `M-SEARCH * HTTP/1.1\r\n` +
|
|
6
|
+
`HOST: ${SSDP_ADDRESS}:${SSDP_PORT}\r\n` +
|
|
7
|
+
`MAN: "ssdp:discover"\r\n` +
|
|
8
|
+
`ST: ${SEARCH_TARGET}\r\n` +
|
|
9
|
+
`MX: 3\r\n\r\n`;
|
|
10
|
+
let cachedHost = null;
|
|
11
|
+
function parseLocation(response) {
|
|
12
|
+
const match = /^LOCATION:\s*(.+)/im.exec(response);
|
|
13
|
+
return match ? match[1].trim() : null;
|
|
14
|
+
}
|
|
15
|
+
function parseUsn(response) {
|
|
16
|
+
const match = /^USN:\s*(.+)/im.exec(response);
|
|
17
|
+
return match ? match[1].trim() : '';
|
|
18
|
+
}
|
|
19
|
+
function extractHost(locationUrl) {
|
|
20
|
+
try {
|
|
21
|
+
const url = new URL(locationUrl);
|
|
22
|
+
return url.hostname;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
const match = /\/\/([^:/]+)/i.exec(locationUrl);
|
|
26
|
+
return match ? match[1] : locationUrl;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export async function discoverRokuDevices(timeoutMs = 3000) {
|
|
30
|
+
return new Promise((resolve) => {
|
|
31
|
+
const devices = [];
|
|
32
|
+
const seen = new Set();
|
|
33
|
+
const socket = dgram.createSocket({ type: 'udp4', reuseAddr: true });
|
|
34
|
+
const timeout = setTimeout(() => {
|
|
35
|
+
socket.close();
|
|
36
|
+
resolve(devices);
|
|
37
|
+
}, timeoutMs);
|
|
38
|
+
socket.on('message', (msg) => {
|
|
39
|
+
const response = msg.toString();
|
|
40
|
+
if (!response.toLowerCase().includes('roku'))
|
|
41
|
+
return;
|
|
42
|
+
const location = parseLocation(response);
|
|
43
|
+
if (!location)
|
|
44
|
+
return;
|
|
45
|
+
const host = extractHost(location);
|
|
46
|
+
if (seen.has(host))
|
|
47
|
+
return;
|
|
48
|
+
seen.add(host);
|
|
49
|
+
devices.push({
|
|
50
|
+
host,
|
|
51
|
+
location,
|
|
52
|
+
usn: parseUsn(response),
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
socket.on('error', () => {
|
|
56
|
+
clearTimeout(timeout);
|
|
57
|
+
socket.close();
|
|
58
|
+
resolve(devices);
|
|
59
|
+
});
|
|
60
|
+
socket.bind(() => {
|
|
61
|
+
const message = Buffer.from(M_SEARCH);
|
|
62
|
+
socket.send(message, 0, message.length, SSDP_PORT, SSDP_ADDRESS);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
export async function discoverFirstHost() {
|
|
67
|
+
if (cachedHost)
|
|
68
|
+
return cachedHost;
|
|
69
|
+
const devices = await discoverRokuDevices();
|
|
70
|
+
if (devices.length > 0) {
|
|
71
|
+
cachedHost = devices[0].host;
|
|
72
|
+
return cachedHost;
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
export function clearDiscoveryCache() {
|
|
77
|
+
cachedHost = null;
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=discovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discovery.js","sourceRoot":"","sources":["../src/discovery.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import { registerScreenshotTools } from './tools/screenshot.js';
|
|
|
8
8
|
import { registerConsoleTools } from './tools/console.js';
|
|
9
9
|
const server = new McpServer({
|
|
10
10
|
name: 'roku-mcp',
|
|
11
|
-
version: '1.
|
|
11
|
+
version: '1.2.1',
|
|
12
12
|
});
|
|
13
13
|
registerDeployTools(server);
|
|
14
14
|
registerEcpTools(server);
|
package/dist/roku-config.d.ts
CHANGED
|
@@ -5,8 +5,9 @@ export interface RokuDeviceConfig {
|
|
|
5
5
|
export declare function resolveConfig(params: {
|
|
6
6
|
host?: string;
|
|
7
7
|
password?: string;
|
|
8
|
-
}): RokuDeviceConfig
|
|
8
|
+
}): Promise<RokuDeviceConfig>;
|
|
9
9
|
export declare function resolveHost(params: {
|
|
10
10
|
host?: string;
|
|
11
|
-
}): string
|
|
11
|
+
}): Promise<string>;
|
|
12
|
+
export declare function friendlyError(error: unknown): string;
|
|
12
13
|
//# sourceMappingURL=roku-config.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"roku-config.d.ts","sourceRoot":"","sources":["../src/roku-config.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"roku-config.d.ts","sourceRoot":"","sources":["../src/roku-config.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,wBAAsB,aAAa,CAAC,MAAM,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAgB3G;AAED,wBAAsB,WAAW,CAAC,MAAM,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAQ5E;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAuBpD"}
|
package/dist/roku-config.js
CHANGED
|
@@ -1,19 +1,42 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { discoverFirstHost } from './discovery.js';
|
|
2
|
+
export async function resolveConfig(params) {
|
|
3
|
+
const host = params.host || process.env.ROKU_DEVICE_HOST || await discoverFirstHost();
|
|
3
4
|
const password = params.password || process.env.ROKU_DEVICE_PASSWORD;
|
|
4
5
|
if (!host) {
|
|
5
|
-
throw new Error('Roku device
|
|
6
|
+
throw new Error('No Roku device found. Please provide the device IP as a parameter, set ROKU_DEVICE_HOST in your .env file, or make sure a Roku device is powered on and connected to the same network.');
|
|
6
7
|
}
|
|
7
8
|
if (!password) {
|
|
8
|
-
throw new Error('Roku
|
|
9
|
+
throw new Error('Roku developer password is required. Please provide the password as a parameter or set ROKU_DEVICE_PASSWORD in your .env file.');
|
|
9
10
|
}
|
|
10
11
|
return { host, password };
|
|
11
12
|
}
|
|
12
|
-
export function resolveHost(params) {
|
|
13
|
-
const host = params.host || process.env.ROKU_DEVICE_HOST;
|
|
13
|
+
export async function resolveHost(params) {
|
|
14
|
+
const host = params.host || process.env.ROKU_DEVICE_HOST || await discoverFirstHost();
|
|
14
15
|
if (!host) {
|
|
15
|
-
throw new Error('Roku device
|
|
16
|
+
throw new Error('No Roku device found. Please provide the device IP as a parameter, set ROKU_DEVICE_HOST in your .env file, or make sure a Roku device is powered on and connected to the same network.');
|
|
16
17
|
}
|
|
17
18
|
return host;
|
|
18
19
|
}
|
|
20
|
+
export function friendlyError(error) {
|
|
21
|
+
const raw = error instanceof Error ? error.message : String(error);
|
|
22
|
+
if (/401|unauthorized/i.test(raw)) {
|
|
23
|
+
return 'Authentication failed — the Roku developer password is incorrect. Please provide the correct password.';
|
|
24
|
+
}
|
|
25
|
+
if (/403|forbidden/i.test(raw)) {
|
|
26
|
+
return 'Access denied by the Roku device. Make sure Developer Mode is enabled and the password is correct.';
|
|
27
|
+
}
|
|
28
|
+
if (/ECONNREFUSED/i.test(raw)) {
|
|
29
|
+
return `Connection refused — the Roku device is not reachable. Verify the IP address is correct and the device is powered on.`;
|
|
30
|
+
}
|
|
31
|
+
if (/ETIMEDOUT|ESOCKETTIMEDOUT|timeout/i.test(raw)) {
|
|
32
|
+
return 'Connection timed out — the Roku device did not respond. Check that it is on the same network and not blocked by a firewall.';
|
|
33
|
+
}
|
|
34
|
+
if (/ENOTFOUND|getaddrinfo/i.test(raw)) {
|
|
35
|
+
return `Could not resolve the Roku device hostname. Verify the host address is correct.`;
|
|
36
|
+
}
|
|
37
|
+
if (/No screen shot url/i.test(raw)) {
|
|
38
|
+
return 'Screenshot failed — no sideloaded dev channel is currently running on the device. Deploy an app first.';
|
|
39
|
+
}
|
|
40
|
+
return raw;
|
|
41
|
+
}
|
|
19
42
|
//# sourceMappingURL=roku-config.js.map
|
package/dist/roku-config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"roku-config.js","sourceRoot":"","sources":["../src/roku-config.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"roku-config.js","sourceRoot":"","sources":["../src/roku-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAOnD,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,MAA4C;IAC9E,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,MAAM,iBAAiB,EAAE,CAAC;IACtF,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,IAAI,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC;IAErE,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,IAAI,KAAK,CACb,wLAAwL,CACzL,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,IAAI,KAAK,CACb,gIAAgI,CACjI,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;AAC5B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,MAAyB;IACzD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,MAAM,iBAAiB,EAAE,CAAC;IACtF,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,IAAI,KAAK,CACb,wLAAwL,CACzL,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAAc;IAC1C,MAAM,GAAG,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAEnE,IAAI,mBAAmB,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QAClC,OAAO,wGAAwG,CAAC;IAClH,CAAC;IACD,IAAI,gBAAgB,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QAC/B,OAAO,oGAAoG,CAAC;IAC9G,CAAC;IACD,IAAI,eAAe,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QAC9B,OAAO,uHAAuH,CAAC;IACjI,CAAC;IACD,IAAI,oCAAoC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACnD,OAAO,6HAA6H,CAAC;IACvI,CAAC;IACD,IAAI,wBAAwB,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACvC,OAAO,iFAAiF,CAAC;IAC3F,CAAC;IACD,IAAI,qBAAqB,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACpC,OAAO,wGAAwG,CAAC;IAClH,CAAC;IAED,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"console.d.ts","sourceRoot":"","sources":["../../src/tools/console.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAcpE,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"console.d.ts","sourceRoot":"","sources":["../../src/tools/console.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAcpE,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAkL5D"}
|
package/dist/tools/console.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import net from 'net';
|
|
3
|
-
import { resolveHost } from '../roku-config.js';
|
|
3
|
+
import { resolveHost, friendlyError } from '../roku-config.js';
|
|
4
4
|
let session = null;
|
|
5
5
|
export function registerConsoleTools(server) {
|
|
6
6
|
server.registerTool('roku_console_connect', {
|
|
@@ -16,7 +16,7 @@ export function registerConsoleTools(server) {
|
|
|
16
16
|
content: [{ type: 'text', text: `Already connected to ${session.host}. Disconnect first to connect to a different device.` }],
|
|
17
17
|
};
|
|
18
18
|
}
|
|
19
|
-
const host = resolveHost(params);
|
|
19
|
+
const host = await resolveHost(params);
|
|
20
20
|
const port = params.port ?? 8085;
|
|
21
21
|
return await new Promise((resolve) => {
|
|
22
22
|
const socket = new net.Socket();
|
|
@@ -29,7 +29,7 @@ export function registerConsoleTools(server) {
|
|
|
29
29
|
const timeout = setTimeout(() => {
|
|
30
30
|
socket.destroy();
|
|
31
31
|
resolve({
|
|
32
|
-
content: [{ type: 'text', text: `Connection to ${host}:${port} timed out
|
|
32
|
+
content: [{ type: 'text', text: `Connection to ${host}:${port} timed out — make sure the Roku device is on the same network and has a sideloaded dev channel running.` }],
|
|
33
33
|
isError: true,
|
|
34
34
|
});
|
|
35
35
|
}, 10_000);
|
|
@@ -58,7 +58,7 @@ export function registerConsoleTools(server) {
|
|
|
58
58
|
}
|
|
59
59
|
if (!newSession.connected) {
|
|
60
60
|
resolve({
|
|
61
|
-
content: [{ type: 'text', text: `
|
|
61
|
+
content: [{ type: 'text', text: `Console connection failed: ${friendlyError(err)}` }],
|
|
62
62
|
isError: true,
|
|
63
63
|
});
|
|
64
64
|
}
|
|
@@ -66,9 +66,8 @@ export function registerConsoleTools(server) {
|
|
|
66
66
|
});
|
|
67
67
|
}
|
|
68
68
|
catch (error) {
|
|
69
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
70
69
|
return {
|
|
71
|
-
content: [{ type: 'text', text: `Console connect failed: ${
|
|
70
|
+
content: [{ type: 'text', text: `Console connect failed: ${friendlyError(error)}` }],
|
|
72
71
|
isError: true,
|
|
73
72
|
};
|
|
74
73
|
}
|
|
@@ -128,9 +127,8 @@ export function registerConsoleTools(server) {
|
|
|
128
127
|
});
|
|
129
128
|
}
|
|
130
129
|
catch (error) {
|
|
131
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
132
130
|
return {
|
|
133
|
-
content: [{ type: 'text', text: `Console send failed: ${
|
|
131
|
+
content: [{ type: 'text', text: `Console send failed: ${friendlyError(error)}` }],
|
|
134
132
|
isError: true,
|
|
135
133
|
};
|
|
136
134
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"console.js","sourceRoot":"","sources":["../../src/tools/console.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"console.js","sourceRoot":"","sources":["../../src/tools/console.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAS/D,IAAI,OAAO,GAA0B,IAAI,CAAC;AAE1C,MAAM,UAAU,oBAAoB,CAAC,MAAiB;IACpD,MAAM,CAAC,YAAY,CACjB,sBAAsB,EACtB;QACE,WAAW,EAAE,2HAA2H;QACxI,WAAW,EAAE;YACX,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,2CAA2C,CAAC;YACjF,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,mCAAmC,CAAC;SACxF;KACF,EACD,KAAK,EAAE,MAAM,EAAE,EAAE;QACf,IAAI,CAAC;YACH,IAAI,OAAO,EAAE,SAAS,EAAE,CAAC;gBACvB,OAAO;oBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,wBAAwB,OAAO,CAAC,IAAI,sDAAsD,EAAE,CAAC;iBAC9H,CAAC;YACJ,CAAC;YAED,MAAM,IAAI,GAAG,MAAM,WAAW,CAAC,MAAM,CAAC,CAAC;YACvC,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,IAAI,IAAI,CAAC;YAEjC,OAAO,MAAM,IAAI,OAAO,CAAwE,CAAC,OAAO,EAAE,EAAE;gBAC1G,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;gBAChC,MAAM,UAAU,GAAmB;oBACjC,MAAM;oBACN,MAAM,EAAE,EAAE;oBACV,IAAI;oBACJ,SAAS,EAAE,KAAK;iBACjB,CAAC;gBAEF,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE;oBAC9B,MAAM,CAAC,OAAO,EAAE,CAAC;oBACjB,OAAO,CAAC;wBACN,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,IAAI,IAAI,IAAI,yGAAyG,EAAE,CAAC;wBACzK,OAAO,EAAE,IAAI;qBACd,CAAC,CAAC;gBACL,CAAC,EAAE,MAAM,CAAC,CAAC;gBAEX,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE;oBAC9B,YAAY,CAAC,OAAO,CAAC,CAAC;oBACtB,UAAU,CAAC,SAAS,GAAG,IAAI,CAAC;oBAC5B,OAAO,GAAG,UAAU,CAAC;oBACrB,OAAO,CAAC;wBACN,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,8CAA8C,IAAI,IAAI,IAAI,EAAE,EAAE,CAAC;qBAChG,CAAC,CAAC;gBACL,CAAC,CAAC,CAAC;gBAEH,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;oBACzB,IAAI,OAAO,KAAK,UAAU,EAAE,CAAC;wBAC3B,OAAO,CAAC,MAAM,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;oBACpC,CAAC;gBACH,CAAC,CAAC,CAAC;gBAEH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;oBACtB,IAAI,OAAO,KAAK,UAAU,EAAE,CAAC;wBAC3B,OAAO,CAAC,SAAS,GAAG,KAAK,CAAC;oBAC5B,CAAC;gBACH,CAAC,CAAC,CAAC;gBAEH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;oBACzB,YAAY,CAAC,OAAO,CAAC,CAAC;oBACtB,IAAI,OAAO,KAAK,UAAU,EAAE,CAAC;wBAC3B,OAAO,CAAC,SAAS,GAAG,KAAK,CAAC;oBAC5B,CAAC;oBACD,IAAI,CAAC,UAAU,CAAC,SAAS,EAAE,CAAC;wBAC1B,OAAO,CAAC;4BACN,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,8BAA8B,aAAa,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;4BACrF,OAAO,EAAE,IAAI;yBACd,CAAC,CAAC;oBACL,CAAC;gBACH,CAAC,CAAC,CAAC;YACL,CAAC,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,2BAA2B,aAAa,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC;gBACpF,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;IACH,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,mBAAmB,EACnB;QACE,WAAW,EAAE,gHAAgH;KAC9H,EACD,KAAK,IAAI,EAAE;QACT,IAAI,CAAC,OAAO,EAAE,SAAS,EAAE,CAAC;YACxB,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,+DAA+D,EAAE,CAAC;gBAClG,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;QAED,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;QAE7C,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QAC9B,OAAO,CAAC,MAAM,GAAG,EAAE,CAAC;QAEpB,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACxB,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,EAAE,CAAC;aACrD,CAAC;QACJ,CAAC;QAED,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;SAC1C,CAAC;IACJ,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,mBAAmB,EACnB;QACE,WAAW,EAAE,2JAA2J;QACxK,WAAW,EAAE;YACX,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,sCAAsC,CAAC;SACrE;KACF,EACD,KAAK,EAAE,MAAM,EAAE,EAAE;QACf,IAAI,CAAC,OAAO,EAAE,SAAS,EAAE,CAAC;YACxB,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,+DAA+D,EAAE,CAAC;gBAClG,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;QAED,IAAI,CAAC;YACH,OAAO,MAAM,IAAI,OAAO,CAAwE,CAAC,OAAO,EAAE,EAAE;gBAC1G,OAAQ,CAAC,MAAM,GAAG,EAAE,CAAC;gBACrB,OAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,GAAG,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE;oBACrD,IAAI,GAAG,EAAE,CAAC;wBACR,OAAO,CAAC;4BACN,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,2BAA2B,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC;4BAC3E,OAAO,EAAE,IAAI;yBACd,CAAC,CAAC;wBACH,OAAO;oBACT,CAAC;oBAED,UAAU,CAAC,GAAG,EAAE;wBACd,MAAM,MAAM,GAAG,OAAQ,CAAC,MAAM,CAAC;wBAC/B,OAAQ,CAAC,MAAM,GAAG,EAAE,CAAC;wBACrB,OAAO,CAAC;4BACN,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,IAAI,oCAAoC,EAAE,CAAC;yBAClF,CAAC,CAAC;oBACL,CAAC,EAAE,GAAG,CAAC,CAAC;gBACV,CAAC,CAAC,CAAC;YACL,CAAC,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,wBAAwB,aAAa,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC;gBACjF,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;IACH,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,yBAAyB,EACzB;QACE,WAAW,EAAE,yDAAyD;KACvE,EACD,KAAK,IAAI,EAAE;QACT,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,6CAA6C,EAAE,CAAC;aACjF,CAAC;QACJ,CAAC;QAED,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;QAC1B,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACzB,OAAO,GAAG,IAAI,CAAC;QAEf,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,qBAAqB,IAAI,EAAE,EAAE,CAAC;SAC/D,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/tools/deploy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;
|
|
1
|
+
{"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/tools/deploy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAQpE,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA8E3D"}
|
package/dist/tools/deploy.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import os from 'os';
|
|
2
5
|
import { rokuDeploy } from 'roku-deploy';
|
|
3
|
-
import { resolveConfig } from '../roku-config.js';
|
|
6
|
+
import { resolveConfig, friendlyError } from '../roku-config.js';
|
|
4
7
|
export function registerDeployTools(server) {
|
|
5
8
|
server.registerTool('roku_deploy', {
|
|
6
|
-
description: 'Sideload (deploy) a Roku application to the device. A dev channel must not already be running, or it will be replaced.',
|
|
9
|
+
description: 'Sideload (deploy) a Roku application to the device. Packages all files in the project directory and uploads them. A dev channel must not already be running, or it will be replaced.',
|
|
7
10
|
inputSchema: {
|
|
8
11
|
host: z.string().optional().describe('IP address or hostname of the Roku device'),
|
|
9
12
|
password: z.string().optional().describe('Developer password for the Roku device'),
|
|
@@ -11,28 +14,38 @@ export function registerDeployTools(server) {
|
|
|
11
14
|
files: z
|
|
12
15
|
.array(z.string())
|
|
13
16
|
.optional()
|
|
14
|
-
.describe('Glob patterns for files to include. Defaults to
|
|
17
|
+
.describe('Glob patterns for files to include. Defaults to all files in rootDir (**/*).'),
|
|
15
18
|
},
|
|
16
19
|
}, async (params) => {
|
|
17
20
|
try {
|
|
18
|
-
const
|
|
21
|
+
const manifestPath = path.join(params.rootDir, 'manifest');
|
|
22
|
+
try {
|
|
23
|
+
await fs.access(manifestPath);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return {
|
|
27
|
+
content: [{ type: 'text', text: `Deploy failed: No manifest file found at ${manifestPath}. The rootDir must point to the Roku project directory that contains the manifest file.` }],
|
|
28
|
+
isError: true,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
const config = await resolveConfig(params);
|
|
32
|
+
const outDir = path.join(os.tmpdir(), 'roku-mcp-deploy');
|
|
19
33
|
const deployOptions = {
|
|
20
34
|
host: config.host,
|
|
21
35
|
password: config.password,
|
|
22
36
|
rootDir: params.rootDir,
|
|
37
|
+
outDir,
|
|
38
|
+
files: params.files && params.files.length > 0 ? params.files : ['**/*'],
|
|
39
|
+
retainStagingDir: false,
|
|
23
40
|
};
|
|
24
|
-
if (params.files && params.files.length > 0) {
|
|
25
|
-
deployOptions.files = params.files;
|
|
26
|
-
}
|
|
27
41
|
await rokuDeploy.deploy(deployOptions);
|
|
28
42
|
return {
|
|
29
43
|
content: [{ type: 'text', text: `Successfully deployed app from ${params.rootDir} to ${config.host}` }],
|
|
30
44
|
};
|
|
31
45
|
}
|
|
32
46
|
catch (error) {
|
|
33
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
34
47
|
return {
|
|
35
|
-
content: [{ type: 'text', text: `Deploy failed: ${
|
|
48
|
+
content: [{ type: 'text', text: `Deploy failed: ${friendlyError(error)}` }],
|
|
36
49
|
isError: true,
|
|
37
50
|
};
|
|
38
51
|
}
|
|
@@ -45,7 +58,7 @@ export function registerDeployTools(server) {
|
|
|
45
58
|
},
|
|
46
59
|
}, async (params) => {
|
|
47
60
|
try {
|
|
48
|
-
const config = resolveConfig(params);
|
|
61
|
+
const config = await resolveConfig(params);
|
|
49
62
|
await rokuDeploy.deleteInstalledChannel({
|
|
50
63
|
host: config.host,
|
|
51
64
|
password: config.password,
|
|
@@ -55,9 +68,8 @@ export function registerDeployTools(server) {
|
|
|
55
68
|
};
|
|
56
69
|
}
|
|
57
70
|
catch (error) {
|
|
58
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
59
71
|
return {
|
|
60
|
-
content: [{ type: 'text', text: `Delete dev channel failed: ${
|
|
72
|
+
content: [{ type: 'text', text: `Delete dev channel failed: ${friendlyError(error)}` }],
|
|
61
73
|
isError: true,
|
|
62
74
|
};
|
|
63
75
|
}
|
package/dist/tools/deploy.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy.js","sourceRoot":"","sources":["../../src/tools/deploy.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"deploy.js","sourceRoot":"","sources":["../../src/tools/deploy.ts"],"names":[],"mappings":"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"}
|
package/dist/tools/ecp.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ecp.d.ts","sourceRoot":"","sources":["../../src/tools/ecp.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;
|
|
1
|
+
{"version":3,"file":"ecp.d.ts","sourceRoot":"","sources":["../../src/tools/ecp.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAapE,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA4OxD"}
|
package/dist/tools/ecp.js
CHANGED
|
@@ -1,12 +1,38 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import axios from 'axios';
|
|
3
3
|
import { XMLParser } from 'fast-xml-parser';
|
|
4
|
-
import { resolveHost } from '../roku-config.js';
|
|
4
|
+
import { resolveHost, friendlyError } from '../roku-config.js';
|
|
5
|
+
import { discoverRokuDevices } from '../discovery.js';
|
|
5
6
|
const xmlParser = new XMLParser({ ignoreAttributes: false, attributeNamePrefix: '@_' });
|
|
6
7
|
function ecpUrl(host, path) {
|
|
7
8
|
return `http://${host}:8060/${path}`;
|
|
8
9
|
}
|
|
9
10
|
export function registerEcpTools(server) {
|
|
11
|
+
server.registerTool('roku_discover', {
|
|
12
|
+
description: 'Scan the local network for Roku devices using SSDP discovery. Returns a list of all found devices with their IP addresses.',
|
|
13
|
+
inputSchema: {
|
|
14
|
+
timeoutMs: z.number().optional().default(3000).describe('How long to scan in milliseconds (default 3000)'),
|
|
15
|
+
},
|
|
16
|
+
}, async (params) => {
|
|
17
|
+
try {
|
|
18
|
+
const devices = await discoverRokuDevices(params.timeoutMs ?? 3000);
|
|
19
|
+
if (devices.length === 0) {
|
|
20
|
+
return {
|
|
21
|
+
content: [{ type: 'text', text: 'No Roku devices found on the network.' }],
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const list = devices.map((d, i) => `${i + 1}. ${d.host} (${d.location})`).join('\n');
|
|
25
|
+
return {
|
|
26
|
+
content: [{ type: 'text', text: `Found ${devices.length} Roku device(s):\n${list}` }],
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
return {
|
|
31
|
+
content: [{ type: 'text', text: `Discovery failed: ${friendlyError(error)}` }],
|
|
32
|
+
isError: true,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
});
|
|
10
36
|
server.registerTool('roku_keypress', {
|
|
11
37
|
description: 'Send a single key press to the Roku device via ECP. Common keys: Home, Rev, Fwd, Play, Select, Left, Right, Down, Up, Back, InstantReplay, Info, Backspace, Search, Enter, Lit_<character>',
|
|
12
38
|
inputSchema: {
|
|
@@ -15,16 +41,15 @@ export function registerEcpTools(server) {
|
|
|
15
41
|
},
|
|
16
42
|
}, async (params) => {
|
|
17
43
|
try {
|
|
18
|
-
const host = resolveHost(params);
|
|
44
|
+
const host = await resolveHost(params);
|
|
19
45
|
await axios.post(ecpUrl(host, `keypress/${encodeURIComponent(params.key)}`));
|
|
20
46
|
return {
|
|
21
47
|
content: [{ type: 'text', text: `Key press sent: ${params.key}` }],
|
|
22
48
|
};
|
|
23
49
|
}
|
|
24
50
|
catch (error) {
|
|
25
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
26
51
|
return {
|
|
27
|
-
content: [{ type: 'text', text: `Key press failed: ${
|
|
52
|
+
content: [{ type: 'text', text: `Key press failed: ${friendlyError(error)}` }],
|
|
28
53
|
isError: true,
|
|
29
54
|
};
|
|
30
55
|
}
|
|
@@ -42,7 +67,7 @@ export function registerEcpTools(server) {
|
|
|
42
67
|
},
|
|
43
68
|
}, async (params) => {
|
|
44
69
|
try {
|
|
45
|
-
const host = resolveHost(params);
|
|
70
|
+
const host = await resolveHost(params);
|
|
46
71
|
const delay = params.delayMs ?? 100;
|
|
47
72
|
for (const key of params.keys) {
|
|
48
73
|
await axios.post(ecpUrl(host, `keypress/${encodeURIComponent(key)}`));
|
|
@@ -55,9 +80,8 @@ export function registerEcpTools(server) {
|
|
|
55
80
|
};
|
|
56
81
|
}
|
|
57
82
|
catch (error) {
|
|
58
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
59
83
|
return {
|
|
60
|
-
content: [{ type: 'text', text: `Key sequence failed: ${
|
|
84
|
+
content: [{ type: 'text', text: `Key sequence failed: ${friendlyError(error)}` }],
|
|
61
85
|
isError: true,
|
|
62
86
|
};
|
|
63
87
|
}
|
|
@@ -74,7 +98,7 @@ export function registerEcpTools(server) {
|
|
|
74
98
|
},
|
|
75
99
|
}, async (toolParams) => {
|
|
76
100
|
try {
|
|
77
|
-
const host = resolveHost(toolParams);
|
|
101
|
+
const host = await resolveHost(toolParams);
|
|
78
102
|
const queryString = toolParams.params
|
|
79
103
|
? '?' + new URLSearchParams(toolParams.params).toString()
|
|
80
104
|
: '';
|
|
@@ -84,9 +108,8 @@ export function registerEcpTools(server) {
|
|
|
84
108
|
};
|
|
85
109
|
}
|
|
86
110
|
catch (error) {
|
|
87
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
88
111
|
return {
|
|
89
|
-
content: [{ type: 'text', text: `Launch failed: ${
|
|
112
|
+
content: [{ type: 'text', text: `Launch failed: ${friendlyError(error)}` }],
|
|
90
113
|
isError: true,
|
|
91
114
|
};
|
|
92
115
|
}
|
|
@@ -98,7 +121,7 @@ export function registerEcpTools(server) {
|
|
|
98
121
|
},
|
|
99
122
|
}, async (params) => {
|
|
100
123
|
try {
|
|
101
|
-
const host = resolveHost(params);
|
|
124
|
+
const host = await resolveHost(params);
|
|
102
125
|
const response = await axios.get(ecpUrl(host, 'query/device-info'));
|
|
103
126
|
const parsed = xmlParser.parse(response.data);
|
|
104
127
|
return {
|
|
@@ -106,9 +129,8 @@ export function registerEcpTools(server) {
|
|
|
106
129
|
};
|
|
107
130
|
}
|
|
108
131
|
catch (error) {
|
|
109
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
110
132
|
return {
|
|
111
|
-
content: [{ type: 'text', text: `Query device info failed: ${
|
|
133
|
+
content: [{ type: 'text', text: `Query device info failed: ${friendlyError(error)}` }],
|
|
112
134
|
isError: true,
|
|
113
135
|
};
|
|
114
136
|
}
|
|
@@ -120,7 +142,7 @@ export function registerEcpTools(server) {
|
|
|
120
142
|
},
|
|
121
143
|
}, async (params) => {
|
|
122
144
|
try {
|
|
123
|
-
const host = resolveHost(params);
|
|
145
|
+
const host = await resolveHost(params);
|
|
124
146
|
const response = await axios.get(ecpUrl(host, 'query/active-app'));
|
|
125
147
|
const parsed = xmlParser.parse(response.data);
|
|
126
148
|
return {
|
|
@@ -128,9 +150,8 @@ export function registerEcpTools(server) {
|
|
|
128
150
|
};
|
|
129
151
|
}
|
|
130
152
|
catch (error) {
|
|
131
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
132
153
|
return {
|
|
133
|
-
content: [{ type: 'text', text: `Query active app failed: ${
|
|
154
|
+
content: [{ type: 'text', text: `Query active app failed: ${friendlyError(error)}` }],
|
|
134
155
|
isError: true,
|
|
135
156
|
};
|
|
136
157
|
}
|
|
@@ -142,16 +163,15 @@ export function registerEcpTools(server) {
|
|
|
142
163
|
},
|
|
143
164
|
}, async (params) => {
|
|
144
165
|
try {
|
|
145
|
-
const host = resolveHost(params);
|
|
166
|
+
const host = await resolveHost(params);
|
|
146
167
|
const response = await axios.get(ecpUrl(host, 'query/app-ui'));
|
|
147
168
|
return {
|
|
148
169
|
content: [{ type: 'text', text: response.data }],
|
|
149
170
|
};
|
|
150
171
|
}
|
|
151
172
|
catch (error) {
|
|
152
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
153
173
|
return {
|
|
154
|
-
content: [{ type: 'text', text: `Query app UI failed: ${
|
|
174
|
+
content: [{ type: 'text', text: `Query app UI failed: ${friendlyError(error)}` }],
|
|
155
175
|
isError: true,
|
|
156
176
|
};
|
|
157
177
|
}
|
|
@@ -165,7 +185,7 @@ export function registerEcpTools(server) {
|
|
|
165
185
|
},
|
|
166
186
|
}, async (params) => {
|
|
167
187
|
try {
|
|
168
|
-
const host = resolveHost(params);
|
|
188
|
+
const host = await resolveHost(params);
|
|
169
189
|
let path;
|
|
170
190
|
if (params.type === 'node') {
|
|
171
191
|
if (!params.nodeId) {
|
|
@@ -185,9 +205,8 @@ export function registerEcpTools(server) {
|
|
|
185
205
|
};
|
|
186
206
|
}
|
|
187
207
|
catch (error) {
|
|
188
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
189
208
|
return {
|
|
190
|
-
content: [{ type: 'text', text: `Query SG nodes failed: ${
|
|
209
|
+
content: [{ type: 'text', text: `Query SG nodes failed: ${friendlyError(error)}` }],
|
|
191
210
|
isError: true,
|
|
192
211
|
};
|
|
193
212
|
}
|
package/dist/tools/ecp.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ecp.js","sourceRoot":"","sources":["../../src/tools/ecp.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"ecp.js","sourceRoot":"","sources":["../../src/tools/ecp.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"screenshot.d.ts","sourceRoot":"","sources":["../../src/tools/screenshot.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAOpE,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"screenshot.d.ts","sourceRoot":"","sources":["../../src/tools/screenshot.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAOpE,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAsD/D"}
|
package/dist/tools/screenshot.js
CHANGED
|
@@ -2,7 +2,7 @@ import { z } from 'zod';
|
|
|
2
2
|
import { rokuDeploy } from 'roku-deploy';
|
|
3
3
|
import fs from 'fs/promises';
|
|
4
4
|
import path from 'path';
|
|
5
|
-
import { resolveConfig } from '../roku-config.js';
|
|
5
|
+
import { resolveConfig, friendlyError } from '../roku-config.js';
|
|
6
6
|
export function registerScreenshotTools(server) {
|
|
7
7
|
server.registerTool('roku_screenshot', {
|
|
8
8
|
description: 'Capture a screenshot of the current screen on the Roku device. A sideloaded dev channel must be running. Returns the image as base64-encoded content and the file path where it was saved.',
|
|
@@ -14,7 +14,7 @@ export function registerScreenshotTools(server) {
|
|
|
14
14
|
},
|
|
15
15
|
}, async (params) => {
|
|
16
16
|
try {
|
|
17
|
-
const config = resolveConfig(params);
|
|
17
|
+
const config = await resolveConfig(params);
|
|
18
18
|
const options = {
|
|
19
19
|
host: config.host,
|
|
20
20
|
password: config.password,
|
|
@@ -45,9 +45,8 @@ export function registerScreenshotTools(server) {
|
|
|
45
45
|
};
|
|
46
46
|
}
|
|
47
47
|
catch (error) {
|
|
48
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
49
48
|
return {
|
|
50
|
-
content: [{ type: 'text', text: `Screenshot failed: ${
|
|
49
|
+
content: [{ type: 'text', text: `Screenshot failed: ${friendlyError(error)}` }],
|
|
51
50
|
isError: true,
|
|
52
51
|
};
|
|
53
52
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"screenshot.js","sourceRoot":"","sources":["../../src/tools/screenshot.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,MAAM,aAAa,CAAC;AAC7B,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"screenshot.js","sourceRoot":"","sources":["../../src/tools/screenshot.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,MAAM,aAAa,CAAC;AAC7B,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEjE,MAAM,UAAU,uBAAuB,CAAC,MAAiB;IACvD,MAAM,CAAC,YAAY,CACjB,iBAAiB,EACjB;QACE,WAAW,EAAE,4LAA4L;QACzM,WAAW,EAAE;YACX,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,2CAA2C,CAAC;YACjF,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,wCAAwC,CAAC;YAClF,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,kEAAkE,CAAC;YAC1G,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,kEAAkE,CAAC;SAC5G;KACF,EACD,KAAK,EAAE,MAAM,EAAE,EAAE;QACf,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC;YAC3C,MAAM,OAAO,GAA4B;gBACvC,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,QAAQ,EAAE,MAAM,CAAC,QAAQ;aAC1B,CAAC;YAEF,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;gBAClB,OAAO,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;YACjC,CAAC;YACD,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;gBACnB,OAAO,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC;YACnC,CAAC;YAED,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,cAAc,CAAC,OAAc,CAAC,CAAC;YACjE,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAC;YACjD,MAAM,QAAQ,GAAG,GAAG,KAAK,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,YAAY,CAAC;YAC7D,MAAM,WAAW,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;YAChD,MAAM,MAAM,GAAG,WAAW,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;YAE9C,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,OAAgB;wBACtB,IAAI,EAAE,MAAM;wBACZ,QAAQ;qBACT;oBACD;wBACE,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,wBAAwB,QAAQ,EAAE;qBACzC;iBACF;aACF,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,sBAAsB,aAAa,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC;gBAC/E,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
|