firefox-css-theme-mcp 0.1.5 → 0.1.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/README.md +11 -64
- package/index.js +42 -0
- package/package.json +8 -16
- package/dist/firefox.js +0 -188
- package/dist/index.js +0 -161
package/README.md
CHANGED
|
@@ -1,86 +1,33 @@
|
|
|
1
|
-
# Firefox CSS Theme MCP
|
|
1
|
+
# Firefox CSS Theme MCP (Deprecated)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> [!WARNING]
|
|
4
|
+
> This package (`firefox-css-theme-mcp`) has been renamed and superseded by [`firefox-css-theme`](https://www.npmjs.com/package/firefox-css-theme).
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
Please update your configurations to use `firefox-css-theme`.
|
|
6
7
|
|
|
7
|
-
##
|
|
8
|
+
## Migration
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
- Firefox Browser
|
|
11
|
-
|
|
12
|
-
## Installation & Usage
|
|
13
|
-
|
|
14
|
-
### Option A — CLI
|
|
15
|
-
|
|
16
|
-
#### Claude Code
|
|
10
|
+
### Claude Code
|
|
17
11
|
|
|
18
12
|
```bash
|
|
19
|
-
claude mcp add firefox-css-theme -- npx -y firefox-css-theme
|
|
13
|
+
claude mcp add firefox-css-theme -- npx -y firefox-css-theme mcp
|
|
20
14
|
```
|
|
21
15
|
|
|
22
|
-
|
|
16
|
+
### Codex
|
|
23
17
|
|
|
24
18
|
```bash
|
|
25
|
-
codex mcp add firefox-css-theme -- npx -y firefox-css-theme
|
|
19
|
+
codex mcp add firefox-css-theme -- npx -y firefox-css-theme mcp
|
|
26
20
|
```
|
|
27
21
|
|
|
28
|
-
###
|
|
29
|
-
|
|
30
|
-
Add the server configuration to your MCP settings file:
|
|
22
|
+
### MCP Settings File
|
|
31
23
|
|
|
32
24
|
```json
|
|
33
25
|
{
|
|
34
26
|
"mcpServers": {
|
|
35
27
|
"firefox-css-theme": {
|
|
36
28
|
"command": "npx",
|
|
37
|
-
"args": ["-y", "firefox-css-theme
|
|
29
|
+
"args": ["-y", "firefox-css-theme", "mcp"]
|
|
38
30
|
}
|
|
39
31
|
}
|
|
40
32
|
}
|
|
41
33
|
```
|
|
42
|
-
|
|
43
|
-
### Option C — From Source
|
|
44
|
-
|
|
45
|
-
1. Clone the repository and build the project:
|
|
46
|
-
|
|
47
|
-
```bash
|
|
48
|
-
git clone https://github.com/easonwong-de/Firefox-CSS-Theme-MCP.git
|
|
49
|
-
cd Firefox-CSS-Theme-MCP
|
|
50
|
-
npm install
|
|
51
|
-
npm run build
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
2. Add the local build to your MCP configuration:
|
|
55
|
-
|
|
56
|
-
```json
|
|
57
|
-
{
|
|
58
|
-
"mcpServers": {
|
|
59
|
-
"firefox-css-theme": {
|
|
60
|
-
"command": "node",
|
|
61
|
-
"args": [
|
|
62
|
-
"/path/to/Firefox-CSS-Theme-MCP/dist/index.js",
|
|
63
|
-
"--nova-ui"
|
|
64
|
-
]
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
## Command-line Flags
|
|
71
|
-
|
|
72
|
-
| Flag | Type | Description |
|
|
73
|
-
| :---------- | :-------- | :------------------------------------------------------------------------------------------------------------------------------- |
|
|
74
|
-
| `--nova-ui` | `boolean` | Enables the Firefox Nova UI redesign preferences (`browser.nova.enabled` and `browser.newtabpage.activity-stream.nova.enabled`). |
|
|
75
|
-
|
|
76
|
-
## Available MCP Tools
|
|
77
|
-
|
|
78
|
-
- `launch_browser`: Launches Firefox with chrome debugging capabilities enabled.
|
|
79
|
-
- `close_browser`: Terminates the browser instance.
|
|
80
|
-
- `get_ui_tree`: Dumps the hierarchical DOM tree of the chrome window.
|
|
81
|
-
- `query_ui_elements`: Queries elements matching a CSS selector in the browser chrome.
|
|
82
|
-
- `get_computed_styles`: Extracts computed CSS property values of a specific UI element.
|
|
83
|
-
- `inject_theme_css`: Dynamically injects or updates stylesheets in the live UI for instant feedback.
|
|
84
|
-
- `remove_theme_css`: Removes an injected stylesheet.
|
|
85
|
-
- `take_ui_screenshot`: Captures a screenshot of the browser window or a specific UI component.
|
|
86
|
-
- `execute_chrome_javascript`: Runs privileged JavaScript in the browser chrome window context.
|
package/index.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
|
|
5
|
+
console.warn(
|
|
6
|
+
"\x1b[33m[DEPRECATED]\x1b[0m 'firefox-css-theme-mcp' has been renamed to 'firefox-css-theme'. " +
|
|
7
|
+
"Please update your configuration to use 'npx -y firefox-css-theme mcp'.",
|
|
8
|
+
);
|
|
9
|
+
|
|
10
|
+
const arguments_ = ["mcp", ...process.argv.slice(2)];
|
|
11
|
+
|
|
12
|
+
const childProcess = spawn("firefox-css-theme", arguments_, {
|
|
13
|
+
stdio: "inherit",
|
|
14
|
+
shell: process.platform === "win32",
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
childProcess.on("error", (error) => {
|
|
18
|
+
if (error && "code" in error && error.code === "ENOENT") {
|
|
19
|
+
const fallbackProcess = spawn(
|
|
20
|
+
"npx",
|
|
21
|
+
["-y", "firefox-css-theme@latest", ...arguments_],
|
|
22
|
+
{ stdio: "inherit", shell: process.platform === "win32" },
|
|
23
|
+
);
|
|
24
|
+
fallbackProcess.on("exit", (code, signal) => {
|
|
25
|
+
if (signal) {
|
|
26
|
+
process.kill(process.pid, signal);
|
|
27
|
+
} else {
|
|
28
|
+
process.exit(code ?? 0);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
throw error;
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
childProcess.on("exit", (code, signal) => {
|
|
37
|
+
if (signal) {
|
|
38
|
+
process.kill(process.pid, signal);
|
|
39
|
+
} else {
|
|
40
|
+
process.exit(code ?? 0);
|
|
41
|
+
}
|
|
42
|
+
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "firefox-css-theme-mcp",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.7",
|
|
4
|
+
"description": "DEPRECATED: Renamed to firefox-css-theme. Use 'npx -y firefox-css-theme mcp' instead.",
|
|
5
5
|
"homepage": "https://github.com/easonwong-de/Firefox-CSS-Theme-MCP#readme",
|
|
6
6
|
"bugs": {
|
|
7
7
|
"url": "https://github.com/easonwong-de/Firefox-CSS-Theme-MCP/issues"
|
|
@@ -13,28 +13,20 @@
|
|
|
13
13
|
"license": "MIT",
|
|
14
14
|
"author": "Eason Wong <me@easonwong.de> (https://easonwong.de/)",
|
|
15
15
|
"type": "module",
|
|
16
|
-
"main": "
|
|
16
|
+
"main": "index.js",
|
|
17
17
|
"bin": {
|
|
18
|
-
"firefox-css-theme-mcp": "
|
|
18
|
+
"firefox-css-theme-mcp": "index.js"
|
|
19
19
|
},
|
|
20
20
|
"files": [
|
|
21
|
-
"
|
|
21
|
+
"index.js"
|
|
22
22
|
],
|
|
23
23
|
"scripts": {
|
|
24
|
-
"
|
|
25
|
-
"format": "prettier --write --list-different .",
|
|
26
|
-
"prepublishOnly": "npm run build",
|
|
27
|
-
"start": "node dist/index.js"
|
|
24
|
+
"format": "prettier --write --list-different ."
|
|
28
25
|
},
|
|
29
26
|
"dependencies": {
|
|
30
|
-
"
|
|
31
|
-
"selenium-webdriver": "^4.47.0",
|
|
32
|
-
"zod": "^4.4.3"
|
|
27
|
+
"firefox-css-theme": ">=0.1.0"
|
|
33
28
|
},
|
|
34
29
|
"devDependencies": {
|
|
35
|
-
"
|
|
36
|
-
"@types/selenium-webdriver": "^4.35.6",
|
|
37
|
-
"prettier": "^3.9.6",
|
|
38
|
-
"typescript": "^7.0.2"
|
|
30
|
+
"prettier": "^3.9.6"
|
|
39
31
|
}
|
|
40
32
|
}
|
package/dist/firefox.js
DELETED
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
import { Builder, By } from "selenium-webdriver";
|
|
2
|
-
import * as firefox from "selenium-webdriver/firefox.js";
|
|
3
|
-
export class FirefoxBrowserManager {
|
|
4
|
-
driverInstance = null;
|
|
5
|
-
async initialiseBrowser(binaryPath, profileDirectory) {
|
|
6
|
-
if (this.driverInstance)
|
|
7
|
-
return;
|
|
8
|
-
const isNovaUiEnabled = process.argv.includes("--nova-ui");
|
|
9
|
-
const firefoxOptions = new firefox.Options();
|
|
10
|
-
firefoxOptions.addArguments("-no-remote", "-new-instance");
|
|
11
|
-
firefoxOptions.setPreference("devtools.chrome.enabled", true);
|
|
12
|
-
firefoxOptions.setPreference("devtools.debugger.remote-enabled", true);
|
|
13
|
-
firefoxOptions.setPreference("devtools.debugger.prompt-connection", false);
|
|
14
|
-
firefoxOptions.setPreference("toolkit.legacyUserProfileCustomizations.stylesheets", true);
|
|
15
|
-
firefoxOptions.setPreference("marionette.allow-system-access", true);
|
|
16
|
-
firefoxOptions.setPreference("remote.allow-system-access", true);
|
|
17
|
-
firefoxOptions.setPreference("browser.nova.enabled", isNovaUiEnabled);
|
|
18
|
-
firefoxOptions.setPreference("browser.newtabpage.activity-stream.nova.enabled", isNovaUiEnabled);
|
|
19
|
-
if (binaryPath)
|
|
20
|
-
firefoxOptions.setBinary(binaryPath);
|
|
21
|
-
if (profileDirectory)
|
|
22
|
-
firefoxOptions.setProfile(profileDirectory);
|
|
23
|
-
const firefoxService = new firefox.ServiceBuilder()
|
|
24
|
-
.enableVerboseLogging(true)
|
|
25
|
-
.addArguments("--allow-system-access");
|
|
26
|
-
try {
|
|
27
|
-
this.driverInstance = await new Builder()
|
|
28
|
-
.forBrowser("firefox")
|
|
29
|
-
.setFirefoxOptions(firefoxOptions)
|
|
30
|
-
.setFirefoxService(firefoxService)
|
|
31
|
-
.build();
|
|
32
|
-
await this.ensureChromeContext();
|
|
33
|
-
await this.openBrowserToolbox().catch(() => { });
|
|
34
|
-
}
|
|
35
|
-
catch (error) {
|
|
36
|
-
if (this.driverInstance) {
|
|
37
|
-
await this.driverInstance.quit().catch(() => { });
|
|
38
|
-
this.driverInstance = null;
|
|
39
|
-
}
|
|
40
|
-
throw error;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
async terminateBrowser() {
|
|
44
|
-
if (!this.driverInstance)
|
|
45
|
-
return;
|
|
46
|
-
await this.driverInstance.quit();
|
|
47
|
-
this.driverInstance = null;
|
|
48
|
-
}
|
|
49
|
-
async ensureChromeContext() {
|
|
50
|
-
if (!this.driverInstance) {
|
|
51
|
-
throw new Error("Firefox driver instance is not initialised.");
|
|
52
|
-
}
|
|
53
|
-
await this.driverInstance.setContext("chrome");
|
|
54
|
-
}
|
|
55
|
-
async executeChromeScript(script, ...argumentsList) {
|
|
56
|
-
await this.ensureChromeContext();
|
|
57
|
-
return (await this.driverInstance.executeScript(script, ...argumentsList));
|
|
58
|
-
}
|
|
59
|
-
async queryElements(selector) {
|
|
60
|
-
await this.ensureChromeContext();
|
|
61
|
-
return await this.executeChromeScript((querySelector) => {
|
|
62
|
-
const matchedElements = Array.from(document.querySelectorAll(querySelector));
|
|
63
|
-
return matchedElements.map((element) => {
|
|
64
|
-
const attributeMap = {};
|
|
65
|
-
for (const attribute of Array.from(element.attributes)) {
|
|
66
|
-
attributeMap[attribute.name] = attribute.value;
|
|
67
|
-
}
|
|
68
|
-
return {
|
|
69
|
-
tagName: element.tagName.toLowerCase(),
|
|
70
|
-
id: element.id || "",
|
|
71
|
-
className: element.className || "",
|
|
72
|
-
childCount: element.children.length,
|
|
73
|
-
textContent: (element.textContent || "")
|
|
74
|
-
.trim()
|
|
75
|
-
.slice(0, 100),
|
|
76
|
-
attributes: attributeMap,
|
|
77
|
-
};
|
|
78
|
-
});
|
|
79
|
-
}, selector);
|
|
80
|
-
}
|
|
81
|
-
async getComputedStyles(selector, stylePropertyNames) {
|
|
82
|
-
await this.ensureChromeContext();
|
|
83
|
-
return await this.executeChromeScript((targetSelector, requestedProperties) => {
|
|
84
|
-
const targetElement = document.querySelector(targetSelector);
|
|
85
|
-
if (!targetElement) {
|
|
86
|
-
throw new Error("Element not found for selector: " + targetSelector);
|
|
87
|
-
}
|
|
88
|
-
const computedStyleDeclaration = window.getComputedStyle(targetElement);
|
|
89
|
-
const styleResult = {};
|
|
90
|
-
if (Array.isArray(requestedProperties) &&
|
|
91
|
-
requestedProperties.length > 0) {
|
|
92
|
-
for (const propertyName of requestedProperties) {
|
|
93
|
-
styleResult[propertyName] =
|
|
94
|
-
computedStyleDeclaration.getPropertyValue(propertyName);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
else {
|
|
98
|
-
for (let index = 0; index < computedStyleDeclaration.length; index++) {
|
|
99
|
-
const propertyName = computedStyleDeclaration[index];
|
|
100
|
-
styleResult[propertyName] =
|
|
101
|
-
computedStyleDeclaration.getPropertyValue(propertyName);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
return styleResult;
|
|
105
|
-
}, selector, stylePropertyNames);
|
|
106
|
-
}
|
|
107
|
-
async getUserInterfaceTree(rootSelector = "window", maximumDepth = 3) {
|
|
108
|
-
await this.ensureChromeContext();
|
|
109
|
-
return await this.executeChromeScript((targetSelector, maximumTraversalDepth) => {
|
|
110
|
-
const rootTarget = targetSelector === "window"
|
|
111
|
-
? document.documentElement
|
|
112
|
-
: document.querySelector(targetSelector);
|
|
113
|
-
if (!rootTarget) {
|
|
114
|
-
throw new Error("Root element not found for selector: " +
|
|
115
|
-
targetSelector);
|
|
116
|
-
}
|
|
117
|
-
function serializeNode(node, currentDepth) {
|
|
118
|
-
const attributeMap = {};
|
|
119
|
-
for (const attribute of Array.from(node.attributes)) {
|
|
120
|
-
attributeMap[attribute.name] = attribute.value;
|
|
121
|
-
}
|
|
122
|
-
const serializedChildren = [];
|
|
123
|
-
if (currentDepth < maximumTraversalDepth) {
|
|
124
|
-
for (const child of Array.from(node.children)) {
|
|
125
|
-
serializedChildren.push(serializeNode(child, currentDepth + 1));
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
return {
|
|
129
|
-
tagName: node.tagName.toLowerCase(),
|
|
130
|
-
id: node.id || "",
|
|
131
|
-
className: node.className || "",
|
|
132
|
-
attributes: attributeMap,
|
|
133
|
-
children: serializedChildren,
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
return serializeNode(rootTarget, 0);
|
|
137
|
-
}, rootSelector, maximumDepth);
|
|
138
|
-
}
|
|
139
|
-
async injectUserInterfaceStyle(cascadingStyleSheetContent, styleIdentifier = "mcp-injected-style") {
|
|
140
|
-
await this.ensureChromeContext();
|
|
141
|
-
return await this.executeChromeScript((identifier, cssSource) => {
|
|
142
|
-
let existingStyleElement = document.getElementById(identifier);
|
|
143
|
-
if (!existingStyleElement) {
|
|
144
|
-
existingStyleElement = document.createElement("style");
|
|
145
|
-
existingStyleElement.id = identifier;
|
|
146
|
-
existingStyleElement.setAttribute("type", "text/css");
|
|
147
|
-
document.documentElement.appendChild(existingStyleElement);
|
|
148
|
-
}
|
|
149
|
-
existingStyleElement.textContent = cssSource;
|
|
150
|
-
return { success: true, identifier: identifier };
|
|
151
|
-
}, styleIdentifier, cascadingStyleSheetContent);
|
|
152
|
-
}
|
|
153
|
-
async removeUserInterfaceStyle(styleIdentifier) {
|
|
154
|
-
await this.ensureChromeContext();
|
|
155
|
-
return await this.executeChromeScript((identifier) => {
|
|
156
|
-
const targetStyleElement = document.getElementById(identifier);
|
|
157
|
-
if (targetStyleElement) {
|
|
158
|
-
targetStyleElement.remove();
|
|
159
|
-
return { removed: true, identifier: identifier };
|
|
160
|
-
}
|
|
161
|
-
return { removed: false, identifier: identifier };
|
|
162
|
-
}, styleIdentifier);
|
|
163
|
-
}
|
|
164
|
-
async captureScreenshot(targetSelector) {
|
|
165
|
-
await this.ensureChromeContext();
|
|
166
|
-
if (targetSelector) {
|
|
167
|
-
const targetWebElement = await this.driverInstance.findElement(By.css(targetSelector));
|
|
168
|
-
const elementScreenshotBase64 = await targetWebElement.takeScreenshot();
|
|
169
|
-
return { base64Image: elementScreenshotBase64, format: "png" };
|
|
170
|
-
}
|
|
171
|
-
const fullWindowScreenshotBase64 = await this.driverInstance.takeScreenshot();
|
|
172
|
-
return { base64Image: fullWindowScreenshotBase64, format: "png" };
|
|
173
|
-
}
|
|
174
|
-
async openBrowserToolbox() {
|
|
175
|
-
await this.ensureChromeContext();
|
|
176
|
-
return await this.executeChromeScript(() => {
|
|
177
|
-
try {
|
|
178
|
-
const { BrowserToolboxLauncher } = ChromeUtils.importESModule("resource://devtools/client/framework/browser-toolbox/Launcher.sys.mjs");
|
|
179
|
-
BrowserToolboxLauncher.init();
|
|
180
|
-
return { success: true };
|
|
181
|
-
}
|
|
182
|
-
catch {
|
|
183
|
-
return { success: false };
|
|
184
|
-
}
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
export const globalFirefoxManager = new FirefoxBrowserManager();
|
package/dist/index.js
DELETED
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
-
import { z } from "zod";
|
|
5
|
-
import { globalFirefoxManager } from "./firefox.js";
|
|
6
|
-
const serverInstance = new McpServer({
|
|
7
|
-
name: "firefox-css-theme-mcp",
|
|
8
|
-
version: "0.1.5",
|
|
9
|
-
});
|
|
10
|
-
serverInstance.registerTool("launch_browser", {
|
|
11
|
-
description: "Launch a Firefox instance with chrome debugging capabilities enabled.",
|
|
12
|
-
inputSchema: {
|
|
13
|
-
binaryPath: z
|
|
14
|
-
.string()
|
|
15
|
-
.optional()
|
|
16
|
-
.describe("Optional path to Firefox executable binary."),
|
|
17
|
-
profileDirectory: z
|
|
18
|
-
.string()
|
|
19
|
-
.optional()
|
|
20
|
-
.describe("Optional path to custom Firefox profile directory."),
|
|
21
|
-
},
|
|
22
|
-
}, async (parameters) => {
|
|
23
|
-
await globalFirefoxManager.initialiseBrowser(parameters.binaryPath, parameters.profileDirectory);
|
|
24
|
-
return {
|
|
25
|
-
content: [
|
|
26
|
-
{
|
|
27
|
-
type: "text",
|
|
28
|
-
text: "Firefox launched successfully in chrome context.",
|
|
29
|
-
},
|
|
30
|
-
],
|
|
31
|
-
};
|
|
32
|
-
});
|
|
33
|
-
serverInstance.registerTool("close_browser", { description: "Close the running Firefox instance." }, async () => {
|
|
34
|
-
await globalFirefoxManager.terminateBrowser();
|
|
35
|
-
return {
|
|
36
|
-
content: [{ type: "text", text: "Firefox closed successfully." }],
|
|
37
|
-
};
|
|
38
|
-
});
|
|
39
|
-
serverInstance.registerTool("get_ui_tree", {
|
|
40
|
-
description: "Retrieve the hierarchical DOM tree of the Firefox chrome user interface window.",
|
|
41
|
-
inputSchema: {
|
|
42
|
-
rootSelector: z
|
|
43
|
-
.string()
|
|
44
|
-
.default("window")
|
|
45
|
-
.describe("CSS selector for root element."),
|
|
46
|
-
maximumDepth: z
|
|
47
|
-
.number()
|
|
48
|
-
.default(3)
|
|
49
|
-
.describe("Maximum recursive depth to traverse."),
|
|
50
|
-
},
|
|
51
|
-
}, async (parameters) => {
|
|
52
|
-
const treeData = await globalFirefoxManager.getUserInterfaceTree(parameters.rootSelector, parameters.maximumDepth);
|
|
53
|
-
return {
|
|
54
|
-
content: [
|
|
55
|
-
{ type: "text", text: JSON.stringify(treeData, null, 2) },
|
|
56
|
-
],
|
|
57
|
-
};
|
|
58
|
-
});
|
|
59
|
-
serverInstance.registerTool("query_ui_elements", {
|
|
60
|
-
description: "Find elements in the Firefox UI chrome document matching a CSS selector.",
|
|
61
|
-
inputSchema: {
|
|
62
|
-
selector: z
|
|
63
|
-
.string()
|
|
64
|
-
.describe("CSS selector to query in chrome document (e.g. '#nav-bar', '#TabsToolbar')."),
|
|
65
|
-
},
|
|
66
|
-
}, async (parameters) => {
|
|
67
|
-
const elements = await globalFirefoxManager.queryElements(parameters.selector);
|
|
68
|
-
return {
|
|
69
|
-
content: [
|
|
70
|
-
{ type: "text", text: JSON.stringify(elements, null, 2) },
|
|
71
|
-
],
|
|
72
|
-
};
|
|
73
|
-
});
|
|
74
|
-
serverInstance.registerTool("get_computed_styles", {
|
|
75
|
-
description: "Extract computed CSS properties for a Firefox UI chrome element.",
|
|
76
|
-
inputSchema: {
|
|
77
|
-
selector: z
|
|
78
|
-
.string()
|
|
79
|
-
.describe("CSS selector of the UI element (e.g. '#urlbar-background')."),
|
|
80
|
-
properties: z
|
|
81
|
-
.array(z.string())
|
|
82
|
-
.optional()
|
|
83
|
-
.describe("Optional list of specific CSS property names."),
|
|
84
|
-
},
|
|
85
|
-
}, async (parameters) => {
|
|
86
|
-
const styles = await globalFirefoxManager.getComputedStyles(parameters.selector, parameters.properties);
|
|
87
|
-
return {
|
|
88
|
-
content: [{ type: "text", text: JSON.stringify(styles, null, 2) }],
|
|
89
|
-
};
|
|
90
|
-
});
|
|
91
|
-
serverInstance.registerTool("inject_theme_css", {
|
|
92
|
-
description: "Inject or replace a live stylesheet in the Firefox chrome window for immediate visual testing.",
|
|
93
|
-
inputSchema: {
|
|
94
|
-
css: z
|
|
95
|
-
.string()
|
|
96
|
-
.describe("CSS rules to inject into chrome document."),
|
|
97
|
-
styleId: z
|
|
98
|
-
.string()
|
|
99
|
-
.default("mcp-injected-style")
|
|
100
|
-
.describe("Identifier for the style tag."),
|
|
101
|
-
},
|
|
102
|
-
}, async (parameters) => {
|
|
103
|
-
const result = await globalFirefoxManager.injectUserInterfaceStyle(parameters.css, parameters.styleId);
|
|
104
|
-
return {
|
|
105
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
106
|
-
};
|
|
107
|
-
});
|
|
108
|
-
serverInstance.registerTool("remove_theme_css", {
|
|
109
|
-
description: "Remove an injected stylesheet from the Firefox chrome window.",
|
|
110
|
-
inputSchema: {
|
|
111
|
-
styleId: z
|
|
112
|
-
.string()
|
|
113
|
-
.describe("Identifier of the style tag to remove."),
|
|
114
|
-
},
|
|
115
|
-
}, async (parameters) => {
|
|
116
|
-
const result = await globalFirefoxManager.removeUserInterfaceStyle(parameters.styleId);
|
|
117
|
-
return {
|
|
118
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
119
|
-
};
|
|
120
|
-
});
|
|
121
|
-
serverInstance.registerTool("take_ui_screenshot", {
|
|
122
|
-
description: "Capture a PNG screenshot of the Firefox browser chrome window or a specific UI component.",
|
|
123
|
-
inputSchema: {
|
|
124
|
-
selector: z
|
|
125
|
-
.string()
|
|
126
|
-
.optional()
|
|
127
|
-
.describe("Optional CSS selector to screenshot a specific element."),
|
|
128
|
-
},
|
|
129
|
-
}, async (parameters) => {
|
|
130
|
-
const screenshot = await globalFirefoxManager.captureScreenshot(parameters.selector);
|
|
131
|
-
return {
|
|
132
|
-
content: [
|
|
133
|
-
{
|
|
134
|
-
type: "image",
|
|
135
|
-
data: screenshot.base64Image,
|
|
136
|
-
mimeType: "image/png",
|
|
137
|
-
},
|
|
138
|
-
],
|
|
139
|
-
};
|
|
140
|
-
});
|
|
141
|
-
serverInstance.registerTool("execute_chrome_javascript", {
|
|
142
|
-
description: "Execute privileged JavaScript code in the Firefox chrome window context.",
|
|
143
|
-
inputSchema: {
|
|
144
|
-
script: z
|
|
145
|
-
.string()
|
|
146
|
-
.describe("JavaScript code string to evaluate in chrome context."),
|
|
147
|
-
},
|
|
148
|
-
}, async (parameters) => {
|
|
149
|
-
const result = await globalFirefoxManager.executeChromeScript(parameters.script);
|
|
150
|
-
return {
|
|
151
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
152
|
-
};
|
|
153
|
-
});
|
|
154
|
-
async function main() {
|
|
155
|
-
const transportInstance = new StdioServerTransport();
|
|
156
|
-
await serverInstance.connect(transportInstance);
|
|
157
|
-
}
|
|
158
|
-
main().catch((error) => {
|
|
159
|
-
console.error("Server initialisation failure:", error);
|
|
160
|
-
process.exit(1);
|
|
161
|
-
});
|