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 CHANGED
@@ -1,86 +1,33 @@
1
- # Firefox CSS Theme MCP
1
+ # Firefox CSS Theme MCP (Deprecated)
2
2
 
3
- Model Context Protocol (MCP) server for inspecting, querying, and live-debugging Firefox UI DOM and CSS themes (`userChrome.css`).
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
- ![MCP Usage Example - Customising Close Tab Button](./assets/image.jpg)
6
+ Please update your configurations to use `firefox-css-theme`.
6
7
 
7
- ## Requirements
8
+ ## Migration
8
9
 
9
- - Node.js >= 18
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-mcp --nova-ui
13
+ claude mcp add firefox-css-theme -- npx -y firefox-css-theme mcp
20
14
  ```
21
15
 
22
- #### Codex
16
+ ### Codex
23
17
 
24
18
  ```bash
25
- codex mcp add firefox-css-theme -- npx -y firefox-css-theme-mcp --nova-ui
19
+ codex mcp add firefox-css-theme -- npx -y firefox-css-theme mcp
26
20
  ```
27
21
 
28
- ### Option B — IDE Configuration
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-mcp", "--nova-ui"]
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.5",
4
- "description": "Model Context Protocol server for inspecting and live-debugging Firefox UI DOM and CSS themes.",
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": "dist/index.js",
16
+ "main": "index.js",
17
17
  "bin": {
18
- "firefox-css-theme-mcp": "dist/index.js"
18
+ "firefox-css-theme-mcp": "index.js"
19
19
  },
20
20
  "files": [
21
- "dist"
21
+ "index.js"
22
22
  ],
23
23
  "scripts": {
24
- "build": "tsc",
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
- "@modelcontextprotocol/sdk": "^1.30.0",
31
- "selenium-webdriver": "^4.47.0",
32
- "zod": "^4.4.3"
27
+ "firefox-css-theme": ">=0.1.0"
33
28
  },
34
29
  "devDependencies": {
35
- "@types/node": "^26.2.0",
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
- });