@customaise/mcp 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +172 -0
- package/dist/extension-bridge.d.ts +44 -0
- package/dist/extension-bridge.d.ts.map +1 -0
- package/dist/extension-bridge.js +141 -0
- package/dist/extension-bridge.js.map +1 -0
- package/dist/file-watcher.d.ts +36 -0
- package/dist/file-watcher.d.ts.map +1 -0
- package/dist/file-watcher.js +124 -0
- package/dist/file-watcher.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +62 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +12 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +472 -0
- package/dist/server.js.map +1 -0
- package/package.json +56 -0
package/README.md
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# @customaise/mcp
|
|
2
|
+
|
|
3
|
+
MCP server that connects AI coding agents to the [Customaise](https://customaise.com) Chrome extension for userscript management and browser automation.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
AI Agent ←(stdio)→ MCP Server ←(WebSocket)→ Customaise Extension
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Quick Start
|
|
10
|
+
|
|
11
|
+
### 1. Install Customaise
|
|
12
|
+
Install the [Customaise Chrome extension](https://customaise.com) and enable **MCP Bridge** in Settings.
|
|
13
|
+
|
|
14
|
+
### 2. Add to your IDE
|
|
15
|
+
|
|
16
|
+
**Cursor** (`.cursor/mcp.json`):
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"mcpServers": {
|
|
20
|
+
"customaise": {
|
|
21
|
+
"command": "npx",
|
|
22
|
+
"args": ["-y", "@customaise/mcp"]
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Claude Desktop** (`claude_desktop_config.json`):
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"mcpServers": {
|
|
32
|
+
"customaise": {
|
|
33
|
+
"command": "npx",
|
|
34
|
+
"args": ["-y", "@customaise/mcp"]
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**Windsurf** (`.windsurf/mcp.json`):
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"mcpServers": {
|
|
44
|
+
"customaise": {
|
|
45
|
+
"command": "npx",
|
|
46
|
+
"args": ["-y", "@customaise/mcp"]
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Antigravity** (`mcp_config.json`):
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"mcpServers": {
|
|
56
|
+
"customaise": {
|
|
57
|
+
"command": "npx",
|
|
58
|
+
"args": ["-y", "@customaise/mcp"]
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 3. Done!
|
|
65
|
+
Your AI agent can now manage userscripts, inspect browser pages, and take screenshots — all through natural language.
|
|
66
|
+
|
|
67
|
+
## Available Tools (12)
|
|
68
|
+
|
|
69
|
+
### Script Lifecycle
|
|
70
|
+
| Tool | Description |
|
|
71
|
+
|------|-------------|
|
|
72
|
+
| `list_scripts` | List all managed userscripts |
|
|
73
|
+
| `import_script` | Pull a script to a local file for editing |
|
|
74
|
+
| `export_script` | Push a local file to Customaise (validates + installs) |
|
|
75
|
+
| `delete_script` | Permanently delete a script |
|
|
76
|
+
| `toggle_script` | Enable or disable a script |
|
|
77
|
+
|
|
78
|
+
### Browser Context
|
|
79
|
+
| Tool | Description |
|
|
80
|
+
|------|-------------|
|
|
81
|
+
| `get_page_context` | DOM snapshot of the current page |
|
|
82
|
+
| `get_console_context` | Console logs, errors, and GM_log output |
|
|
83
|
+
| `list_tabs` | List all open browser tabs |
|
|
84
|
+
|
|
85
|
+
### Testing & Verification
|
|
86
|
+
| Tool | Description |
|
|
87
|
+
|------|-------------|
|
|
88
|
+
| `reload_tab` | Reload a tab to re-inject scripts |
|
|
89
|
+
| `take_screenshot` | Capture a screenshot of the visible tab |
|
|
90
|
+
|
|
91
|
+
### UI Control
|
|
92
|
+
| Tool | Description |
|
|
93
|
+
|------|-------------|
|
|
94
|
+
| `toggle_ui` | Show or hide the Customaise UI overlay |
|
|
95
|
+
|
|
96
|
+
### Batch Operations
|
|
97
|
+
| Tool | Description |
|
|
98
|
+
|------|-------------|
|
|
99
|
+
| `sync_scripts` | Bulk export all scripts to a local directory |
|
|
100
|
+
|
|
101
|
+
## Typical Workflow
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
1. get_page_context → understand the target page
|
|
105
|
+
2. write .user.js file → AI creates the script using IDE tools
|
|
106
|
+
3. export_script → Customaise validates and installs
|
|
107
|
+
4. reload_tab → re-inject the script
|
|
108
|
+
5. get_console_context → check for errors
|
|
109
|
+
6. take_screenshot → verify visual result
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## File Sync
|
|
113
|
+
|
|
114
|
+
Use `sync_scripts` to bulk-export all scripts to a local directory:
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
sync_scripts({ directory: "~/customaise-scripts" })
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
This creates:
|
|
121
|
+
- **One `.user.js` file per script** — filename is derived from the script name (lowercase, hyphens, e.g. `my-cool-script.user.js`)
|
|
122
|
+
- **`.customaise-manifest.json`** — maps filenames to script IDs for round-trip editing
|
|
123
|
+
|
|
124
|
+
### Manifest format
|
|
125
|
+
|
|
126
|
+
```json
|
|
127
|
+
{
|
|
128
|
+
"dark-mode-fix.user.js": "vm_script_1774225715376_lus75sdzn",
|
|
129
|
+
"my-cool-script.user.js": "vm_script_1774225800123_abc12defg"
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Round-trip workflow
|
|
134
|
+
|
|
135
|
+
1. `sync_scripts` → exports all scripts to a directory
|
|
136
|
+
2. Edit any `.user.js` file in your IDE
|
|
137
|
+
3. `export_script` with the file path + `scriptId` from the manifest → updates the script
|
|
138
|
+
4. Omit `scriptId` when calling `export_script` → creates a new script instead
|
|
139
|
+
|
|
140
|
+
### File watcher (auto-export)
|
|
141
|
+
|
|
142
|
+
When `sync_scripts` has been called, the MCP server watches the directory for `.user.js` changes. Saving a file in your IDE automatically pushes it to Customaise — no manual `export_script` call needed.
|
|
143
|
+
|
|
144
|
+
## Configuration
|
|
145
|
+
|
|
146
|
+
| Environment Variable | Default | Description |
|
|
147
|
+
|---------------------|---------|-------------|
|
|
148
|
+
| `CUSTOMAISE_WS_PORT` | `4050` | WebSocket server port |
|
|
149
|
+
|
|
150
|
+
## Requirements
|
|
151
|
+
|
|
152
|
+
- **Node.js** ≥ 18
|
|
153
|
+
- **Chrome** with the Customaise extension installed
|
|
154
|
+
- **MCP Bridge** enabled in Customaise Settings (Power User feature)
|
|
155
|
+
|
|
156
|
+
## Troubleshooting
|
|
157
|
+
|
|
158
|
+
**"Customaise extension is not connected"**
|
|
159
|
+
- Make sure Chrome is running with the Customaise extension
|
|
160
|
+
- Check that MCP Bridge is enabled in extension Settings
|
|
161
|
+
- The extension connects automatically within a few seconds
|
|
162
|
+
|
|
163
|
+
**Port conflict on 4050**
|
|
164
|
+
- Set a different port: `CUSTOMAISE_WS_PORT=4051 npx @customaise/mcp`
|
|
165
|
+
|
|
166
|
+
**Scripts not running after export**
|
|
167
|
+
- Call `reload_tab` to trigger script re-injection
|
|
168
|
+
- Check `@match` pattern matches the current URL
|
|
169
|
+
|
|
170
|
+
## License
|
|
171
|
+
|
|
172
|
+
MIT
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ExtensionBridge — WebSocket server that connects the MCP server to the
|
|
3
|
+
* Customaise Chrome extension.
|
|
4
|
+
*
|
|
5
|
+
* Architecture:
|
|
6
|
+
* MCP Server (stdio) ←→ ExtensionBridge (WS server :4050) ←→ Extension SW (WS client)
|
|
7
|
+
*
|
|
8
|
+
* The MCP Node process hosts the WebSocket server because MV3 service workers
|
|
9
|
+
* cannot run servers. The extension connects as a WebSocket client.
|
|
10
|
+
*/
|
|
11
|
+
export declare class ExtensionBridge {
|
|
12
|
+
private wss;
|
|
13
|
+
private extensionSocket;
|
|
14
|
+
private pending;
|
|
15
|
+
private port;
|
|
16
|
+
private requestTimeoutMs;
|
|
17
|
+
constructor(port?: number, requestTimeoutMs?: number);
|
|
18
|
+
/**
|
|
19
|
+
* Start the WebSocket server and begin listening for the extension client.
|
|
20
|
+
*/
|
|
21
|
+
start(): Promise<void>;
|
|
22
|
+
/**
|
|
23
|
+
* Send a request to the extension and wait for the response.
|
|
24
|
+
* Throws if the extension is not connected or the request times out.
|
|
25
|
+
*/
|
|
26
|
+
request(type: string, args?: Record<string, unknown>): Promise<unknown>;
|
|
27
|
+
/**
|
|
28
|
+
* Whether the extension is currently connected.
|
|
29
|
+
*/
|
|
30
|
+
get isConnected(): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Close the WebSocket server and all connections.
|
|
33
|
+
*/
|
|
34
|
+
close(): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Handle an incoming response from the extension, matching it to a pending request.
|
|
37
|
+
*/
|
|
38
|
+
private _handleResponse;
|
|
39
|
+
/**
|
|
40
|
+
* Log to stderr (stdout is reserved for MCP stdio transport).
|
|
41
|
+
*/
|
|
42
|
+
private _log;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=extension-bridge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extension-bridge.d.ts","sourceRoot":"","sources":["../src/extension-bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AA0BH,qBAAa,eAAe;IAC1B,OAAO,CAAC,GAAG,CAAgC;IAC3C,OAAO,CAAC,eAAe,CAA0B;IACjD,OAAO,CAAC,OAAO,CAAqC;IACpD,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,gBAAgB,CAAS;gBAErB,IAAI,SAAO,EAAE,gBAAgB,SAAS;IAKlD;;OAEG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA8CtB;;;OAGG;IACG,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAsBjF;;OAEG;IACH,IAAI,WAAW,IAAI,OAAO,CAEzB;IAED;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAyB5B;;OAEG;IACH,OAAO,CAAC,eAAe;IAiBvB;;OAEG;IACH,OAAO,CAAC,IAAI;CAGb"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ExtensionBridge — WebSocket server that connects the MCP server to the
|
|
3
|
+
* Customaise Chrome extension.
|
|
4
|
+
*
|
|
5
|
+
* Architecture:
|
|
6
|
+
* MCP Server (stdio) ←→ ExtensionBridge (WS server :4050) ←→ Extension SW (WS client)
|
|
7
|
+
*
|
|
8
|
+
* The MCP Node process hosts the WebSocket server because MV3 service workers
|
|
9
|
+
* cannot run servers. The extension connects as a WebSocket client.
|
|
10
|
+
*/
|
|
11
|
+
import { WebSocketServer, WebSocket } from 'ws';
|
|
12
|
+
import { randomUUID } from 'node:crypto';
|
|
13
|
+
export class ExtensionBridge {
|
|
14
|
+
wss = null;
|
|
15
|
+
extensionSocket = null;
|
|
16
|
+
pending = new Map();
|
|
17
|
+
port;
|
|
18
|
+
requestTimeoutMs;
|
|
19
|
+
constructor(port = 4050, requestTimeoutMs = 30_000) {
|
|
20
|
+
this.port = port;
|
|
21
|
+
this.requestTimeoutMs = requestTimeoutMs;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Start the WebSocket server and begin listening for the extension client.
|
|
25
|
+
*/
|
|
26
|
+
start() {
|
|
27
|
+
return new Promise((resolve, reject) => {
|
|
28
|
+
this.wss = new WebSocketServer({ port: this.port }, () => {
|
|
29
|
+
this._log(`WebSocket server listening on ws://localhost:${this.port}`);
|
|
30
|
+
resolve();
|
|
31
|
+
});
|
|
32
|
+
this.wss.on('error', (err) => {
|
|
33
|
+
this._log(`WebSocket server error: ${err.message}`);
|
|
34
|
+
reject(err);
|
|
35
|
+
});
|
|
36
|
+
this.wss.on('connection', (ws) => {
|
|
37
|
+
this._log('Extension client connected');
|
|
38
|
+
// Only allow one client at a time (the Customaise extension)
|
|
39
|
+
if (this.extensionSocket) {
|
|
40
|
+
this._log('Replacing existing extension connection');
|
|
41
|
+
this.extensionSocket.close(1000, 'Replaced by new connection');
|
|
42
|
+
}
|
|
43
|
+
this.extensionSocket = ws;
|
|
44
|
+
ws.on('message', (data) => {
|
|
45
|
+
try {
|
|
46
|
+
const message = JSON.parse(data.toString());
|
|
47
|
+
this._handleResponse(message);
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
this._log(`Failed to parse message from extension: ${err}`);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
ws.on('close', (code, reason) => {
|
|
54
|
+
this._log(`Extension client disconnected (code=${code}, reason=${reason.toString()})`);
|
|
55
|
+
if (this.extensionSocket === ws) {
|
|
56
|
+
this.extensionSocket = null;
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
ws.on('error', (err) => {
|
|
60
|
+
this._log(`Extension client error: ${err.message}`);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Send a request to the extension and wait for the response.
|
|
67
|
+
* Throws if the extension is not connected or the request times out.
|
|
68
|
+
*/
|
|
69
|
+
async request(type, args = {}) {
|
|
70
|
+
if (!this.extensionSocket || this.extensionSocket.readyState !== WebSocket.OPEN) {
|
|
71
|
+
throw new Error('Customaise extension is not connected. Make sure Chrome is running with the Customaise extension loaded.');
|
|
72
|
+
}
|
|
73
|
+
const id = randomUUID();
|
|
74
|
+
return new Promise((resolve, reject) => {
|
|
75
|
+
const timer = setTimeout(() => {
|
|
76
|
+
this.pending.delete(id);
|
|
77
|
+
reject(new Error(`Request to extension timed out after ${this.requestTimeoutMs}ms (type=${type}, id=${id})`));
|
|
78
|
+
}, this.requestTimeoutMs);
|
|
79
|
+
this.pending.set(id, { resolve, reject, timer });
|
|
80
|
+
const request = { id, type, args };
|
|
81
|
+
this.extensionSocket.send(JSON.stringify(request));
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Whether the extension is currently connected.
|
|
86
|
+
*/
|
|
87
|
+
get isConnected() {
|
|
88
|
+
return this.extensionSocket !== null && this.extensionSocket.readyState === WebSocket.OPEN;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Close the WebSocket server and all connections.
|
|
92
|
+
*/
|
|
93
|
+
async close() {
|
|
94
|
+
// Reject all pending requests
|
|
95
|
+
for (const [id, pending] of this.pending) {
|
|
96
|
+
clearTimeout(pending.timer);
|
|
97
|
+
pending.reject(new Error('Bridge is shutting down'));
|
|
98
|
+
}
|
|
99
|
+
this.pending.clear();
|
|
100
|
+
if (this.extensionSocket) {
|
|
101
|
+
this.extensionSocket.close(1000, 'MCP server shutting down');
|
|
102
|
+
this.extensionSocket = null;
|
|
103
|
+
}
|
|
104
|
+
return new Promise((resolve) => {
|
|
105
|
+
if (this.wss) {
|
|
106
|
+
this.wss.close(() => {
|
|
107
|
+
this.wss = null;
|
|
108
|
+
resolve();
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
resolve();
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Handle an incoming response from the extension, matching it to a pending request.
|
|
118
|
+
*/
|
|
119
|
+
_handleResponse(message) {
|
|
120
|
+
const pending = this.pending.get(message.id);
|
|
121
|
+
if (!pending) {
|
|
122
|
+
this._log(`Received response for unknown request id: ${message.id}`);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
this.pending.delete(message.id);
|
|
126
|
+
clearTimeout(pending.timer);
|
|
127
|
+
if (message.success) {
|
|
128
|
+
pending.resolve(message.result);
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
pending.reject(new Error(message.error || 'Extension returned an error'));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Log to stderr (stdout is reserved for MCP stdio transport).
|
|
136
|
+
*/
|
|
137
|
+
_log(message) {
|
|
138
|
+
process.stderr.write(`[customaise-mcp] ${message}\n`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=extension-bridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extension-bridge.js","sourceRoot":"","sources":["../src/extension-bridge.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FileWatcher — watches a synced scripts directory for .user.js changes
|
|
3
|
+
* and auto-exports modified files back to Customaise.
|
|
4
|
+
*
|
|
5
|
+
* Activated after sync_scripts creates the manifest.
|
|
6
|
+
* Uses native fs.watch() for reliable cross-platform file watching.
|
|
7
|
+
*/
|
|
8
|
+
import { ExtensionBridge } from './extension-bridge.js';
|
|
9
|
+
export declare class FileWatcher {
|
|
10
|
+
private watcher;
|
|
11
|
+
private bridge;
|
|
12
|
+
private directory;
|
|
13
|
+
/** filenames we recently wrote — mute window to prevent re-export loops */
|
|
14
|
+
private muteSet;
|
|
15
|
+
private muteTimeoutMs;
|
|
16
|
+
/** debounce timers per file */
|
|
17
|
+
private debounceTimers;
|
|
18
|
+
private debounceMs;
|
|
19
|
+
constructor(bridge: ExtensionBridge);
|
|
20
|
+
/**
|
|
21
|
+
* Start watching a directory for .user.js changes.
|
|
22
|
+
* Replaces any previous watcher.
|
|
23
|
+
*/
|
|
24
|
+
start(directory: string): void;
|
|
25
|
+
/**
|
|
26
|
+
* Mute a filename to prevent re-export after MCP-initiated writes.
|
|
27
|
+
*/
|
|
28
|
+
muteFile(fileName: string): void;
|
|
29
|
+
/**
|
|
30
|
+
* Stop the file watcher.
|
|
31
|
+
*/
|
|
32
|
+
stop(): void;
|
|
33
|
+
private handleFileChange;
|
|
34
|
+
private exportFile;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=file-watcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-watcher.d.ts","sourceRoot":"","sources":["../src/file-watcher.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAIxD,qBAAa,WAAW;IACtB,OAAO,CAAC,OAAO,CAA0B;IACzC,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,SAAS,CAAc;IAE/B,2EAA2E;IAC3E,OAAO,CAAC,OAAO,CAAqB;IACpC,OAAO,CAAC,aAAa,CAAQ;IAE7B,+BAA+B;IAC/B,OAAO,CAAC,cAAc,CAAoD;IAC1E,OAAO,CAAC,UAAU,CAAO;gBAEb,MAAM,EAAE,eAAe;IAInC;;;OAGG;IACH,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IA8B9B;;OAEG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAOhC;;OAEG;IACH,IAAI,IAAI,IAAI;IAWZ,OAAO,CAAC,gBAAgB;YAqBV,UAAU;CAqCzB"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FileWatcher — watches a synced scripts directory for .user.js changes
|
|
3
|
+
* and auto-exports modified files back to Customaise.
|
|
4
|
+
*
|
|
5
|
+
* Activated after sync_scripts creates the manifest.
|
|
6
|
+
* Uses native fs.watch() for reliable cross-platform file watching.
|
|
7
|
+
*/
|
|
8
|
+
import { watch, readFileSync, existsSync } from 'node:fs';
|
|
9
|
+
import { basename, join } from 'node:path';
|
|
10
|
+
const LOG_PREFIX = '[customaise-mcp:watcher]';
|
|
11
|
+
export class FileWatcher {
|
|
12
|
+
watcher = null;
|
|
13
|
+
bridge;
|
|
14
|
+
directory = '';
|
|
15
|
+
/** filenames we recently wrote — mute window to prevent re-export loops */
|
|
16
|
+
muteSet = new Set();
|
|
17
|
+
muteTimeoutMs = 1000;
|
|
18
|
+
/** debounce timers per file */
|
|
19
|
+
debounceTimers = new Map();
|
|
20
|
+
debounceMs = 500;
|
|
21
|
+
constructor(bridge) {
|
|
22
|
+
this.bridge = bridge;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Start watching a directory for .user.js changes.
|
|
26
|
+
* Replaces any previous watcher.
|
|
27
|
+
*/
|
|
28
|
+
start(directory) {
|
|
29
|
+
// Stop previous watcher if any
|
|
30
|
+
this.stop();
|
|
31
|
+
this.directory = directory;
|
|
32
|
+
const manifestPath = join(directory, '.customaise-manifest.json');
|
|
33
|
+
if (!existsSync(manifestPath)) {
|
|
34
|
+
console.error(`${LOG_PREFIX} No manifest found at ${manifestPath}. Run sync_scripts first.`);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
console.error(`${LOG_PREFIX} Watching ${directory} for .user.js changes`);
|
|
38
|
+
// Use native fs.watch on the directory
|
|
39
|
+
this.watcher = watch(directory, (eventType, filename) => {
|
|
40
|
+
if (!filename || !filename.endsWith('.user.js'))
|
|
41
|
+
return;
|
|
42
|
+
if (eventType === 'change' || eventType === 'rename') {
|
|
43
|
+
const filePath = join(directory, filename);
|
|
44
|
+
if (existsSync(filePath)) {
|
|
45
|
+
this.handleFileChange(filePath);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
this.watcher.on('error', (error) => {
|
|
50
|
+
console.error(`${LOG_PREFIX} Watcher error:`, error.message);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Mute a filename to prevent re-export after MCP-initiated writes.
|
|
55
|
+
*/
|
|
56
|
+
muteFile(fileName) {
|
|
57
|
+
this.muteSet.add(fileName);
|
|
58
|
+
setTimeout(() => {
|
|
59
|
+
this.muteSet.delete(fileName);
|
|
60
|
+
}, this.muteTimeoutMs);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Stop the file watcher.
|
|
64
|
+
*/
|
|
65
|
+
stop() {
|
|
66
|
+
if (this.watcher) {
|
|
67
|
+
this.watcher.close();
|
|
68
|
+
this.watcher = null;
|
|
69
|
+
console.error(`${LOG_PREFIX} Watcher stopped`);
|
|
70
|
+
}
|
|
71
|
+
this.debounceTimers.forEach((timer) => clearTimeout(timer));
|
|
72
|
+
this.debounceTimers.clear();
|
|
73
|
+
this.muteSet.clear();
|
|
74
|
+
}
|
|
75
|
+
handleFileChange(filePath) {
|
|
76
|
+
const fileName = basename(filePath);
|
|
77
|
+
// Skip if this file is in the mute window (MCP-initiated write)
|
|
78
|
+
if (this.muteSet.has(fileName)) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
// Debounce — IDEs may trigger multiple save events
|
|
82
|
+
const existing = this.debounceTimers.get(fileName);
|
|
83
|
+
if (existing)
|
|
84
|
+
clearTimeout(existing);
|
|
85
|
+
this.debounceTimers.set(fileName, setTimeout(() => {
|
|
86
|
+
this.debounceTimers.delete(fileName);
|
|
87
|
+
this.exportFile(filePath, fileName);
|
|
88
|
+
}, this.debounceMs));
|
|
89
|
+
}
|
|
90
|
+
async exportFile(filePath, fileName) {
|
|
91
|
+
try {
|
|
92
|
+
// Read manifest to find scriptId
|
|
93
|
+
const manifestPath = join(this.directory, '.customaise-manifest.json');
|
|
94
|
+
if (!existsSync(manifestPath))
|
|
95
|
+
return;
|
|
96
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
|
|
97
|
+
const scriptId = manifest[fileName];
|
|
98
|
+
if (!scriptId) {
|
|
99
|
+
console.error(`${LOG_PREFIX} No scriptId in manifest for ${fileName} — skipping`);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
// Read file content
|
|
103
|
+
const code = readFileSync(filePath, 'utf-8');
|
|
104
|
+
if (!code.trim())
|
|
105
|
+
return;
|
|
106
|
+
console.error(`${LOG_PREFIX} Auto-exporting ${fileName} → ${scriptId}`);
|
|
107
|
+
// Send to extension via bridge
|
|
108
|
+
const result = await this.bridge.request('export_script', {
|
|
109
|
+
code,
|
|
110
|
+
scriptId,
|
|
111
|
+
});
|
|
112
|
+
if (result?.success) {
|
|
113
|
+
console.error(`${LOG_PREFIX} ✓ ${fileName} exported successfully`);
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
console.error(`${LOG_PREFIX} ✗ ${fileName} export failed: ${result?.error || 'unknown'}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
console.error(`${LOG_PREFIX} Error exporting ${fileName}:`, error instanceof Error ? error.message : error);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=file-watcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-watcher.js","sourceRoot":"","sources":["../src/file-watcher.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Customaise MCP Server — Entry Point
|
|
4
|
+
*
|
|
5
|
+
* Connects AI coding agents (Cursor, Antigravity, Claude Code) to the
|
|
6
|
+
* Customaise Chrome extension via the Model Context Protocol.
|
|
7
|
+
*
|
|
8
|
+
* Transport:
|
|
9
|
+
* AI Agent ←(stdio)→ MCP Server ←(WebSocket)→ Chrome Extension
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* node dist/index.js
|
|
13
|
+
*
|
|
14
|
+
* Environment:
|
|
15
|
+
* CUSTOMAISE_WS_PORT — WebSocket server port (default: 4050)
|
|
16
|
+
*/
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;;;;GAcG"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Customaise MCP Server — Entry Point
|
|
4
|
+
*
|
|
5
|
+
* Connects AI coding agents (Cursor, Antigravity, Claude Code) to the
|
|
6
|
+
* Customaise Chrome extension via the Model Context Protocol.
|
|
7
|
+
*
|
|
8
|
+
* Transport:
|
|
9
|
+
* AI Agent ←(stdio)→ MCP Server ←(WebSocket)→ Chrome Extension
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* node dist/index.js
|
|
13
|
+
*
|
|
14
|
+
* Environment:
|
|
15
|
+
* CUSTOMAISE_WS_PORT — WebSocket server port (default: 4050)
|
|
16
|
+
*/
|
|
17
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
18
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
19
|
+
import { registerTools, registerPromptsAndResources } from './server.js';
|
|
20
|
+
import { ExtensionBridge } from './extension-bridge.js';
|
|
21
|
+
import { FileWatcher } from './file-watcher.js';
|
|
22
|
+
const WS_PORT = Number(process.env.CUSTOMAISE_WS_PORT || process.env.VIBEMONKEY_WS_PORT) || 4050;
|
|
23
|
+
async function main() {
|
|
24
|
+
// 1. Start the WebSocket server for the extension to connect to
|
|
25
|
+
const bridge = new ExtensionBridge(WS_PORT);
|
|
26
|
+
await bridge.start();
|
|
27
|
+
// 2. Create the MCP server
|
|
28
|
+
const server = new McpServer({
|
|
29
|
+
name: 'customaise',
|
|
30
|
+
version: '1.0.0'
|
|
31
|
+
}, {
|
|
32
|
+
capabilities: {
|
|
33
|
+
tools: {},
|
|
34
|
+
prompts: {},
|
|
35
|
+
resources: {}
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
// 3. Create file watcher for sync_scripts auto-export
|
|
39
|
+
const fileWatcher = new FileWatcher(bridge);
|
|
40
|
+
// 4. Register tools, prompts, and resources
|
|
41
|
+
registerTools(server, bridge, fileWatcher);
|
|
42
|
+
registerPromptsAndResources(server, bridge);
|
|
43
|
+
// 4. Connect via stdio transport (AI agent communicates over stdin/stdout)
|
|
44
|
+
const transport = new StdioServerTransport();
|
|
45
|
+
await server.connect(transport);
|
|
46
|
+
process.stderr.write('[customaise-mcp] MCP server running (stdio + WebSocket)\n');
|
|
47
|
+
// Graceful shutdown
|
|
48
|
+
const shutdown = async () => {
|
|
49
|
+
process.stderr.write('[customaise-mcp] Shutting down...\n');
|
|
50
|
+
fileWatcher.stop();
|
|
51
|
+
await bridge.close();
|
|
52
|
+
await server.close();
|
|
53
|
+
process.exit(0);
|
|
54
|
+
};
|
|
55
|
+
process.on('SIGINT', shutdown);
|
|
56
|
+
process.on('SIGTERM', shutdown);
|
|
57
|
+
}
|
|
58
|
+
main().catch((err) => {
|
|
59
|
+
process.stderr.write(`[customaise-mcp] Fatal error: ${err.message}\n`);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
});
|
|
62
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,aAAa,EAAE,2BAA2B,EAAE,MAAM,aAAa,CAAC;AACzE,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,IAAI,IAAI,CAAC;AAEjG,KAAK,UAAU,IAAI;IACjB,gEAAgE;IAChE,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC,OAAO,CAAC,CAAC;IAC5C,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;IAErB,2BAA2B;IAC3B,MAAM,MAAM,GAAG,IAAI,SAAS,CAC1B;QACE,IAAI,EAAE,YAAY;QAClB,OAAO,EAAE,OAAO;KACjB,EACD;QACE,YAAY,EAAE;YACZ,KAAK,EAAE,EAAE;YACT,OAAO,EAAE,EAAE;YACX,SAAS,EAAE,EAAE;SACd;KACF,CACF,CAAC;IAEF,sDAAsD;IACtD,MAAM,WAAW,GAAG,IAAI,WAAW,CAAC,MAAM,CAAC,CAAC;IAE5C,4CAA4C;IAC5C,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC;IAC3C,2BAA2B,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAE5C,2EAA2E;IAC3E,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAEhC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,2DAA2D,CAAC,CAAC;IAElF,oBAAoB;IACpB,MAAM,QAAQ,GAAG,KAAK,IAAI,EAAE;QAC1B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,qCAAqC,CAAC,CAAC;QAC5D,WAAW,CAAC,IAAI,EAAE,CAAC;QACnB,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;QACrB,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;QACrB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC;IAEF,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC/B,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;AAClC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,iCAAiC,GAAG,CAAC,OAAO,IAAI,CAAC,CAAC;IACvE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { ExtensionBridge } from './extension-bridge.js';
|
|
3
|
+
import { FileWatcher } from './file-watcher.js';
|
|
4
|
+
/**
|
|
5
|
+
* Register all MCP tools with the server.
|
|
6
|
+
*/
|
|
7
|
+
export declare function registerTools(server: McpServer, bridge: ExtensionBridge, fileWatcher?: FileWatcher): void;
|
|
8
|
+
/**
|
|
9
|
+
* Register MCP Prompts and Resources.
|
|
10
|
+
*/
|
|
11
|
+
export declare function registerPromptsAndResources(server: McpServer, bridge: ExtensionBridge): void;
|
|
12
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAoB,MAAM,yCAAyC,CAAC;AAKtF,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,EAAE,WAAW,CAAC,EAAE,WAAW,GAAG,IAAI,CA0UzG;AAED;;GAEG;AACH,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAqJ5F"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
import { ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
4
|
+
import { dirname, join } from 'node:path';
|
|
5
|
+
import { tmpdir } from 'node:os';
|
|
6
|
+
/**
|
|
7
|
+
* Register all MCP tools with the server.
|
|
8
|
+
*/
|
|
9
|
+
export function registerTools(server, bridge, fileWatcher) {
|
|
10
|
+
// ─── Script Lifecycle ───────────────────────────────────────────────
|
|
11
|
+
server.tool('list_scripts', 'List all userscripts managed by Customaise. Returns each script\'s ID, name, enabled status, URL match patterns, and description. Use this to discover available scripts before importing or modifying them.', {}, async () => {
|
|
12
|
+
const result = await bridge.request('list_scripts', {});
|
|
13
|
+
return {
|
|
14
|
+
content: [{
|
|
15
|
+
type: 'text',
|
|
16
|
+
text: JSON.stringify(result, null, 2)
|
|
17
|
+
}]
|
|
18
|
+
};
|
|
19
|
+
});
|
|
20
|
+
server.tool('import_script', 'Import an existing userscript from Customaise to a local file for editing. The file will include the full source code with metadata block. After editing the file with your IDE tools, use export_script to push changes back to Customaise.', {
|
|
21
|
+
scriptId: z.string().describe('The ID of the script to import (get from list_scripts)'),
|
|
22
|
+
filePath: z.string().describe('Local file path to write the script to (e.g., ./scripts/my-script.user.js)')
|
|
23
|
+
}, async ({ scriptId, filePath }) => {
|
|
24
|
+
const result = await bridge.request('import_script', { scriptId });
|
|
25
|
+
// Ensure directory exists and write file
|
|
26
|
+
mkdirSync(dirname(filePath), { recursive: true });
|
|
27
|
+
writeFileSync(filePath, result.source, 'utf-8');
|
|
28
|
+
return {
|
|
29
|
+
content: [{
|
|
30
|
+
type: 'text',
|
|
31
|
+
text: JSON.stringify({
|
|
32
|
+
success: true,
|
|
33
|
+
filePath,
|
|
34
|
+
scriptId: result.scriptId,
|
|
35
|
+
metadata: result.metadata,
|
|
36
|
+
bytesWritten: Buffer.byteLength(result.source, 'utf-8')
|
|
37
|
+
}, null, 2)
|
|
38
|
+
}]
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
server.tool('export_script', 'Export a userscript from a local file into Customaise. The file will be validated through Customaise\'s sanitization pipeline (syntax checking, AST validation, security analysis). If valid, the script is installed and ready to execute on matching pages. If invalid, detailed diagnostics explain exactly what to fix. Pass scriptId to update an existing script instead of creating a new one.', {
|
|
42
|
+
filePath: z.string().describe('Local file path containing the userscript source code'),
|
|
43
|
+
scriptId: z.string().optional().describe('ID of an existing script to update. Omit to create a new script.')
|
|
44
|
+
}, async ({ filePath, scriptId }) => {
|
|
45
|
+
const code = readFileSync(filePath, 'utf-8');
|
|
46
|
+
const result = await bridge.request('export_script', { code, scriptId });
|
|
47
|
+
return {
|
|
48
|
+
content: [{
|
|
49
|
+
type: 'text',
|
|
50
|
+
text: JSON.stringify(result, null, 2)
|
|
51
|
+
}]
|
|
52
|
+
};
|
|
53
|
+
});
|
|
54
|
+
server.tool('delete_script', 'Permanently delete a userscript from Customaise. This action cannot be undone.', {
|
|
55
|
+
scriptId: z.string().describe('The ID of the script to delete')
|
|
56
|
+
}, async ({ scriptId }) => {
|
|
57
|
+
const result = await bridge.request('delete_script', { scriptId });
|
|
58
|
+
return {
|
|
59
|
+
content: [{
|
|
60
|
+
type: 'text',
|
|
61
|
+
text: JSON.stringify(result, null, 2)
|
|
62
|
+
}]
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
|
+
server.tool('toggle_script', 'Enable or disable a userscript. Disabled scripts are not injected into matching pages. Use this to temporarily turn off a script without deleting it.', {
|
|
66
|
+
scriptId: z.string().describe('The ID of the script to enable/disable'),
|
|
67
|
+
enabled: z.boolean().describe('true to enable, false to disable')
|
|
68
|
+
}, async ({ scriptId, enabled }) => {
|
|
69
|
+
const result = await bridge.request('set_script_enabled', { scriptId, enabled });
|
|
70
|
+
return {
|
|
71
|
+
content: [{
|
|
72
|
+
type: 'text',
|
|
73
|
+
text: JSON.stringify(result, null, 2)
|
|
74
|
+
}]
|
|
75
|
+
};
|
|
76
|
+
});
|
|
77
|
+
// ─── Browser Context ────────────────────────────────────────────────
|
|
78
|
+
server.tool('get_page_context', 'Get a DOM snapshot of the current page including URL, title, page structure, and visible elements. Use this to understand the page layout before writing userscripts that manipulate it.', {
|
|
79
|
+
tabId: z.number().optional().describe('Tab ID to inspect. Defaults to the active tab.')
|
|
80
|
+
}, async ({ tabId }) => {
|
|
81
|
+
const result = await bridge.request('get_page_context', { tabId });
|
|
82
|
+
return {
|
|
83
|
+
content: [{
|
|
84
|
+
type: 'text',
|
|
85
|
+
text: JSON.stringify(result, null, 2)
|
|
86
|
+
}]
|
|
87
|
+
};
|
|
88
|
+
});
|
|
89
|
+
server.tool('get_console_context', 'Get console logs from the browser, including errors, warnings, and userscript GM_log output. Use after reload_tab to check for script runtime errors. Logs include userscript-specific entries by default.', {
|
|
90
|
+
tabId: z.number().optional().describe('Tab ID to get logs from. Defaults to the active tab.'),
|
|
91
|
+
level: z.enum(['all', 'error', 'warn', 'info', 'debug']).optional().describe('Filter by log level. Default: all')
|
|
92
|
+
}, async ({ tabId, level }) => {
|
|
93
|
+
const result = await bridge.request('get_console_context', { tabId });
|
|
94
|
+
// Apply level filter client-side — ConsoleContextService returns
|
|
95
|
+
// pre-structured arrays (errors, warnings, userscriptLogs).
|
|
96
|
+
if (level && level !== 'all') {
|
|
97
|
+
const filtered = { ...result };
|
|
98
|
+
if (level === 'error') {
|
|
99
|
+
delete filtered.warnings;
|
|
100
|
+
delete filtered.userscriptLogs;
|
|
101
|
+
}
|
|
102
|
+
else if (level === 'warn') {
|
|
103
|
+
delete filtered.errors;
|
|
104
|
+
delete filtered.userscriptLogs;
|
|
105
|
+
}
|
|
106
|
+
else if (level === 'info' || level === 'debug') {
|
|
107
|
+
delete filtered.errors;
|
|
108
|
+
delete filtered.warnings;
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
content: [{
|
|
112
|
+
type: 'text',
|
|
113
|
+
text: JSON.stringify(filtered, null, 2)
|
|
114
|
+
}]
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
content: [{
|
|
119
|
+
type: 'text',
|
|
120
|
+
text: JSON.stringify(result, null, 2)
|
|
121
|
+
}]
|
|
122
|
+
};
|
|
123
|
+
});
|
|
124
|
+
server.tool('list_tabs', 'List all open browser tabs with their IDs, URLs, titles, and active status. Use to find a specific tab ID for other tools like reload_tab or take_screenshot.', {}, async () => {
|
|
125
|
+
const result = await bridge.request('list_tabs', {});
|
|
126
|
+
return {
|
|
127
|
+
content: [{
|
|
128
|
+
type: 'text',
|
|
129
|
+
text: JSON.stringify(result, null, 2)
|
|
130
|
+
}]
|
|
131
|
+
};
|
|
132
|
+
});
|
|
133
|
+
server.tool('reload_tab', 'Reload a browser tab to re-inject updated userscripts. Use after export_script to see the effect of your changes. Waits for the page to fully load before returning.', {
|
|
134
|
+
tabId: z.number().optional().describe('Tab ID to reload. Defaults to the active tab.')
|
|
135
|
+
}, async ({ tabId }) => {
|
|
136
|
+
const result = await bridge.request('reload_tab', { tabId });
|
|
137
|
+
return {
|
|
138
|
+
content: [{
|
|
139
|
+
type: 'text',
|
|
140
|
+
text: JSON.stringify(result, null, 2)
|
|
141
|
+
}]
|
|
142
|
+
};
|
|
143
|
+
});
|
|
144
|
+
server.tool('take_screenshot', 'Capture a screenshot of the current visible browser tab. Use to verify visual changes made by userscripts. The screenshot is saved as a PNG file.', {
|
|
145
|
+
tabId: z.number().optional().describe('Tab ID to screenshot. Defaults to the active tab.'),
|
|
146
|
+
filePath: z.string().optional().describe('Local file path to save the screenshot. Auto-generates a temp path if omitted.')
|
|
147
|
+
}, async ({ tabId, filePath }) => {
|
|
148
|
+
const result = await bridge.request('take_screenshot', { tabId });
|
|
149
|
+
// Write base64 PNG data to file
|
|
150
|
+
const savePath = filePath || join(tmpdir(), `customaise-screenshot-${Date.now()}.png`);
|
|
151
|
+
const base64Data = result.dataUrl.replace(/^data:image\/png;base64,/, '');
|
|
152
|
+
mkdirSync(dirname(savePath), { recursive: true });
|
|
153
|
+
writeFileSync(savePath, Buffer.from(base64Data, 'base64'));
|
|
154
|
+
return {
|
|
155
|
+
content: [{
|
|
156
|
+
type: 'text',
|
|
157
|
+
text: JSON.stringify({
|
|
158
|
+
success: true,
|
|
159
|
+
filePath: savePath,
|
|
160
|
+
width: result.width,
|
|
161
|
+
height: result.height
|
|
162
|
+
}, null, 2)
|
|
163
|
+
}]
|
|
164
|
+
};
|
|
165
|
+
});
|
|
166
|
+
server.tool('toggle_ui', 'Show or hide the Customaise UI overlay on the active tab. Use this to make the Customaise interface visible or dismiss it — AI agents cannot click the extension icon directly. Optionally specify which panel to open.', {
|
|
167
|
+
tabId: z.number().optional().describe('Tab ID to toggle UI on. Defaults to the active tab.'),
|
|
168
|
+
panel: z.string().optional().describe('Panel to open: "scripts", "chat", "settings"')
|
|
169
|
+
}, async ({ tabId, panel }) => {
|
|
170
|
+
const result = await bridge.request('show_ui', { tabId, panel });
|
|
171
|
+
return {
|
|
172
|
+
content: [{
|
|
173
|
+
type: 'text',
|
|
174
|
+
text: JSON.stringify(result, null, 2)
|
|
175
|
+
}]
|
|
176
|
+
};
|
|
177
|
+
});
|
|
178
|
+
// ─── File Sync ──────────────────────────────────────────────────────
|
|
179
|
+
server.tool('sync_scripts', 'Bulk export all scripts from Customaise to a local directory as individual .user.js files. Creates a .customaise-manifest.json mapping filenames to script IDs. Use this to set up a local workspace for editing scripts with your IDE.', {
|
|
180
|
+
directory: z.string().describe('Local directory to export scripts to (e.g., ./customaise-scripts/)')
|
|
181
|
+
}, async ({ directory }) => {
|
|
182
|
+
// Get all scripts with code
|
|
183
|
+
const scripts = await bridge.request('list_scripts_with_code', {});
|
|
184
|
+
mkdirSync(directory, { recursive: true });
|
|
185
|
+
const manifest = {};
|
|
186
|
+
let filesWritten = 0;
|
|
187
|
+
const usedNames = new Set();
|
|
188
|
+
for (const script of scripts) {
|
|
189
|
+
// Generate a safe filename from the script name
|
|
190
|
+
let safeName = (script.name || 'untitled')
|
|
191
|
+
.toLowerCase()
|
|
192
|
+
.replace(/[^a-z0-9_-]/g, '-')
|
|
193
|
+
.replace(/-+/g, '-')
|
|
194
|
+
.replace(/^-|-$/g, '');
|
|
195
|
+
// Handle filename collisions — append short ID suffix if name already used
|
|
196
|
+
let fileName = `${safeName}.user.js`;
|
|
197
|
+
if (usedNames.has(fileName)) {
|
|
198
|
+
const idSuffix = script.id.slice(-6);
|
|
199
|
+
fileName = `${safeName}-${idSuffix}.user.js`;
|
|
200
|
+
}
|
|
201
|
+
usedNames.add(fileName);
|
|
202
|
+
const filePath = `${directory}/${fileName}`;
|
|
203
|
+
// Mute each file to prevent the watcher from re-exporting
|
|
204
|
+
if (fileWatcher)
|
|
205
|
+
fileWatcher.muteFile(fileName);
|
|
206
|
+
writeFileSync(filePath, script.code || '', 'utf-8');
|
|
207
|
+
manifest[fileName] = script.id;
|
|
208
|
+
filesWritten++;
|
|
209
|
+
}
|
|
210
|
+
// Write manifest for ID mapping
|
|
211
|
+
const manifestPath = `${directory}/.customaise-manifest.json`;
|
|
212
|
+
writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), 'utf-8');
|
|
213
|
+
// Start file watcher on the synced directory
|
|
214
|
+
if (fileWatcher) {
|
|
215
|
+
fileWatcher.start(directory);
|
|
216
|
+
}
|
|
217
|
+
return {
|
|
218
|
+
content: [{
|
|
219
|
+
type: 'text',
|
|
220
|
+
text: JSON.stringify({
|
|
221
|
+
success: true,
|
|
222
|
+
directory,
|
|
223
|
+
filesWritten,
|
|
224
|
+
manifestPath,
|
|
225
|
+
scripts: Object.entries(manifest).map(([file, id]) => ({ file, id }))
|
|
226
|
+
}, null, 2)
|
|
227
|
+
}]
|
|
228
|
+
};
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Register MCP Prompts and Resources.
|
|
233
|
+
*/
|
|
234
|
+
export function registerPromptsAndResources(server, bridge) {
|
|
235
|
+
// ─── Prompts ────────────────────────────────────────────────────────
|
|
236
|
+
server.prompt('create-userscript', 'Guided workflow for creating a new userscript. Provides a step-by-step prompt that helps the AI agent understand what the user needs and produce a working script.', {
|
|
237
|
+
targetUrl: z.string().describe('The URL pattern the script should match (e.g., *://*.example.com/*)'),
|
|
238
|
+
goal: z.string().describe('What the script should accomplish')
|
|
239
|
+
}, async ({ targetUrl, goal }) => {
|
|
240
|
+
return {
|
|
241
|
+
messages: [
|
|
242
|
+
{
|
|
243
|
+
role: 'user',
|
|
244
|
+
content: {
|
|
245
|
+
type: 'text',
|
|
246
|
+
text: [
|
|
247
|
+
`Create a userscript for Customaise that does the following:`,
|
|
248
|
+
``,
|
|
249
|
+
`**Target URL:** ${targetUrl}`,
|
|
250
|
+
`**Goal:** ${goal}`,
|
|
251
|
+
``,
|
|
252
|
+
`## Requirements`,
|
|
253
|
+
`1. Include a proper metadata block with @name, @match, @description, @version, and @grant directives`,
|
|
254
|
+
`2. Use @match ${targetUrl}`,
|
|
255
|
+
`3. Wrap the script in an IIFE containing named functions (required for symbol-level editing via Customaise)`,
|
|
256
|
+
`4. If making cross-origin requests, include the @connect directive`,
|
|
257
|
+
`5. Use GM_log for debug output (it appears in Customaise's console context)`,
|
|
258
|
+
`6. Handle edge cases (element not found, page still loading, etc.)`,
|
|
259
|
+
``,
|
|
260
|
+
`## Workflow`,
|
|
261
|
+
`1. Use \`get_page_context\` to understand the page structure`,
|
|
262
|
+
`2. Write the script code`,
|
|
263
|
+
`3. For targeting existing page elements, consider using \`VM_findElement\` with \`dom_*\` IDs from \`get_page_context\` for bulletproof selector resilience`,
|
|
264
|
+
`4. Use \`export_script\` to install it in Customaise (it will be validated through the sanitization pipeline)`,
|
|
265
|
+
`5. Use \`reload_tab\` to test it`,
|
|
266
|
+
`6. Use \`get_console_context\` to check for errors or GM_log output`,
|
|
267
|
+
`7. If there are issues, fix and re-export`,
|
|
268
|
+
].join('\n')
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
]
|
|
272
|
+
};
|
|
273
|
+
});
|
|
274
|
+
server.prompt('debug-userscript', 'Debugging workflow for an existing userscript that isn\'t working as expected. Guides the AI through systematic diagnosis using available tools.', {
|
|
275
|
+
scriptId: z.string().describe('The ID of the script to debug')
|
|
276
|
+
}, async ({ scriptId }) => {
|
|
277
|
+
return {
|
|
278
|
+
messages: [
|
|
279
|
+
{
|
|
280
|
+
role: 'user',
|
|
281
|
+
content: {
|
|
282
|
+
type: 'text',
|
|
283
|
+
text: [
|
|
284
|
+
`Debug the userscript with ID \`${scriptId}\`.`,
|
|
285
|
+
``,
|
|
286
|
+
`## Debugging Steps`,
|
|
287
|
+
`1. Use \`import_script\` to pull the script to a local file for inspection`,
|
|
288
|
+
`2. Check if the script is enabled with \`list_scripts\``,
|
|
289
|
+
`3. Use \`list_tabs\` to find a tab matching the script's @match pattern`,
|
|
290
|
+
`4. Use \`reload_tab\` on that tab to trigger script injection`,
|
|
291
|
+
`5. Use \`get_console_context\` to capture errors and GM_log output`,
|
|
292
|
+
`6. Use \`get_page_context\` to verify the DOM state`,
|
|
293
|
+
`7. If needed, use \`take_screenshot\` to see the visual result`,
|
|
294
|
+
``,
|
|
295
|
+
`## Common Issues`,
|
|
296
|
+
`- @match pattern doesn't match the current URL`,
|
|
297
|
+
`- @run-at timing: script runs before target elements exist`,
|
|
298
|
+
`- Missing @grant for GM_* or VM_* APIs used in the script`,
|
|
299
|
+
`- Missing @connect directive for cross-origin GM_xmlhttpRequest calls`,
|
|
300
|
+
`- CSP blocking inline script injection`,
|
|
301
|
+
`- Element selectors changed on the page`,
|
|
302
|
+
``,
|
|
303
|
+
`Fix any issues found and use \`export_script\` to push updates.`,
|
|
304
|
+
].join('\n')
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
]
|
|
308
|
+
};
|
|
309
|
+
});
|
|
310
|
+
// ─── Resources ──────────────────────────────────────────────────────
|
|
311
|
+
server.resource('scripts-list', 'customaise://scripts', {
|
|
312
|
+
description: 'Live list of all userscripts managed by Customaise, including their IDs, names, and enabled status.',
|
|
313
|
+
mimeType: 'application/json'
|
|
314
|
+
}, async (uri) => {
|
|
315
|
+
const result = await bridge.request('list_scripts', {});
|
|
316
|
+
return {
|
|
317
|
+
contents: [{
|
|
318
|
+
uri: uri.href,
|
|
319
|
+
mimeType: 'application/json',
|
|
320
|
+
text: JSON.stringify(result, null, 2)
|
|
321
|
+
}]
|
|
322
|
+
};
|
|
323
|
+
});
|
|
324
|
+
server.resource('script-source', new ResourceTemplate('customaise://scripts/{scriptId}', { list: undefined }), {
|
|
325
|
+
description: 'Full source code and metadata of a specific userscript. Use the script ID from the scripts list.',
|
|
326
|
+
mimeType: 'application/json'
|
|
327
|
+
}, async (uri, variables) => {
|
|
328
|
+
const scriptId = variables.scriptId;
|
|
329
|
+
// Reuse the import_script bridge command (same logic, avoids duplication)
|
|
330
|
+
const result = await bridge.request('import_script', { scriptId });
|
|
331
|
+
return {
|
|
332
|
+
contents: [{
|
|
333
|
+
uri: uri.href,
|
|
334
|
+
mimeType: 'application/json',
|
|
335
|
+
text: JSON.stringify(result, null, 2)
|
|
336
|
+
}]
|
|
337
|
+
};
|
|
338
|
+
});
|
|
339
|
+
server.resource('conventions', 'customaise://conventions', {
|
|
340
|
+
description: 'Userscript writing conventions and best practices for Customaise.',
|
|
341
|
+
mimeType: 'text/markdown'
|
|
342
|
+
}, async (uri) => {
|
|
343
|
+
return {
|
|
344
|
+
contents: [{
|
|
345
|
+
uri: uri.href,
|
|
346
|
+
mimeType: 'text/markdown',
|
|
347
|
+
text: CONVENTIONS_GUIDE
|
|
348
|
+
}]
|
|
349
|
+
};
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
const CONVENTIONS_GUIDE = `# Customaise Userscript Conventions
|
|
353
|
+
|
|
354
|
+
## File Format & Structure
|
|
355
|
+
|
|
356
|
+
Every userscript is a single \`.user.js\` file with a metadata block at the top.
|
|
357
|
+
**CRITICAL**: Customaise supports symbol-level editing (function-by-function). To enable this, your script MUST be wrapped in an IIFE containing **named functions**, rather than flat inline code.
|
|
358
|
+
|
|
359
|
+
\`\`\`javascript
|
|
360
|
+
// ==UserScript==
|
|
361
|
+
// @name My Script
|
|
362
|
+
// @description What this script does
|
|
363
|
+
// @match https://example.com/*
|
|
364
|
+
// @version 1.0
|
|
365
|
+
// @grant GM_log
|
|
366
|
+
// @grant VM_findElement
|
|
367
|
+
// @run-at document-idle
|
|
368
|
+
// ==/UserScript==
|
|
369
|
+
|
|
370
|
+
(function() {
|
|
371
|
+
'use strict';
|
|
372
|
+
|
|
373
|
+
// Good: Named functions allow Customaise to edit them individually
|
|
374
|
+
async function init() {
|
|
375
|
+
GM_log('Script initialized');
|
|
376
|
+
await hideAnnoyingBanner();
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
async function hideAnnoyingBanner() {
|
|
380
|
+
// VM_findElement is our bulletproof DOM selector
|
|
381
|
+
const banner = await VM_findElement('dom_banner_123');
|
|
382
|
+
if (banner) banner.style.display = 'none';
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
init();
|
|
386
|
+
})();
|
|
387
|
+
\`\`\`
|
|
388
|
+
|
|
389
|
+
## Metadata Directives
|
|
390
|
+
|
|
391
|
+
| Directive | Required | Description |
|
|
392
|
+
|-----------|----------|-------------|
|
|
393
|
+
| \`@name\` | ✅ | Script name (must be unique) |
|
|
394
|
+
| \`@match\` | ✅ | URL pattern(s) where the script runs (\`*://*.example.com/*\`) |
|
|
395
|
+
| \`@description\` | Recommended | What the script does |
|
|
396
|
+
| \`@version\` | Recommended | Semantic version (defaults to 1.0) |
|
|
397
|
+
| \`@grant\` | Optional | GM_* or VM_* APIs to enable (use \`none\` for no special APIs) |
|
|
398
|
+
| \`@run-at\` | Optional | When to inject: \`document-start\`, \`document-end\`, \`document-idle\` (default) |
|
|
399
|
+
| \`@connect\` | Optional | Domains allowed for \`GM_xmlhttpRequest\` (e.g., \`api.github.com\`) |
|
|
400
|
+
| \`@domId\` | Auto | Auto-managed by Customaise for \`VM_findElement\`. **Do not edit manually.** |
|
|
401
|
+
| \`@require\` | Optional | External JS libraries to load before the script |
|
|
402
|
+
| \`@resource\` | Optional | Named external resources (CSS, JSON, images) accessible via \`GM_getResourceText/URL\` |
|
|
403
|
+
| \`@namespace\` | Optional | Script namespace (used for de-duplication with imported scripts) |
|
|
404
|
+
| \`@author\` | Optional | Script author |
|
|
405
|
+
|
|
406
|
+
## VM_findElement (Bulletproof DOM Targeting)
|
|
407
|
+
|
|
408
|
+
Customaise provides a revolutionary multi-tier selector API that guarantees 100% element targeting reliability, surviving UI redesigns and dynamic class changes.
|
|
409
|
+
|
|
410
|
+
**Usage:**
|
|
411
|
+
1. You must declare \`@grant VM_findElement\`
|
|
412
|
+
2. Pass a \`dom_*\` ID string (e.g., \`await VM_findElement('dom_1234567890_abc')\`)
|
|
413
|
+
3. **Important:** \`dom_*\` IDs are generated by the user using the Customaise DOM selector tooltip. Do not invent your own \`dom_*\` IDs. If creating elements dynamically, use standard \`document.querySelector\`.
|
|
414
|
+
4. The function is async and must be awaited.
|
|
415
|
+
|
|
416
|
+
**VM_findExternalElement:** Works like \`VM_findElement\` but targets elements inside cross-origin iframes. Requires \`@connect\` for the iframe's domain. Usage: \`await VM_findExternalElement('dom_ext_xxx')\`.
|
|
417
|
+
|
|
418
|
+
## Available GM_* APIs
|
|
419
|
+
|
|
420
|
+
Customaise supports 22 \`GM_*\` APIs, making it highly compatible with existing Greasemonkey/Tampermonkey scripts. You can use either the classic \`GM_*\` (underscore) or modern \`GM.*\` (promise-based) syntax.
|
|
421
|
+
|
|
422
|
+
### Environment & Console
|
|
423
|
+
| API | Description |
|
|
424
|
+
|-----|-------------|
|
|
425
|
+
| \`GM_log(msg)\` / \`GM.log(msg)\` | Log to Customaise console (visible via \`get_console_context\` tool) |
|
|
426
|
+
| \`GM_info\` / \`GM.info\` | Object containing script metadata |
|
|
427
|
+
|
|
428
|
+
### Storage (Extension-Scoped)
|
|
429
|
+
| API | Description |
|
|
430
|
+
|-----|-------------|
|
|
431
|
+
| \`GM_setValue(k, v)\` / \`GM.setValue(k, v)\` | Persistent storage (survives page reloads) |
|
|
432
|
+
| \`GM_getValue(k, def)\` / \`GM.getValue(k, def)\` | Read from persistent storage |
|
|
433
|
+
| \`GM_deleteValue(k)\` / \`GM.deleteValue(k)\` | Delete from persistent storage |
|
|
434
|
+
| \`GM_listValues()\` / \`GM.listValues()\` | List all stored keys |
|
|
435
|
+
| \`GM_addValueChangeListener(name, cb)\` | Listen for storage changes across tabs |
|
|
436
|
+
| \`GM_removeValueChangeListener(id)\` | Remove storage listener |
|
|
437
|
+
|
|
438
|
+
### DOM & UI
|
|
439
|
+
| API | Description |
|
|
440
|
+
|-----|-------------|
|
|
441
|
+
| \`GM_addStyle(css)\` / \`GM.addStyle(css)\` | Inject CSS into the page |
|
|
442
|
+
| \`GM_addElement(tag, attr)\` / \`GM.addElement(tag, attr)\` | Safely create and append DOM elements |
|
|
443
|
+
| \`GM_registerMenuCommand(name, fn)\` | Add a command to the Customaise extension menu |
|
|
444
|
+
| \`GM_unregisterMenuCommand(id)\` | Remove a menu command |
|
|
445
|
+
| \`GM_notification(details)\` / \`GM.notification(details)\` | Show a desktop OS notification |
|
|
446
|
+
|
|
447
|
+
### Network & Resources
|
|
448
|
+
| API | Description |
|
|
449
|
+
|-----|-------------|
|
|
450
|
+
| \`GM_xmlhttpRequest(details)\` | Cross-origin HTTP requests. **Requires \`@connect\` directive.** |
|
|
451
|
+
| \`GM.xmlHttpRequest(details)\` | Promise-based cross-origin HTTP requests |
|
|
452
|
+
| \`GM_download(details)\` | Download a file to disk |
|
|
453
|
+
| \`GM_getResourceText(name)\` / \`GM.getResourceText(name)\` | Read text content from a \`@resource\` |
|
|
454
|
+
| \`GM_getResourceURL(name)\` / \`GM.getResourceUrl(name)\` | Get base64 data URI for a \`@resource\` |
|
|
455
|
+
|
|
456
|
+
### Tabs & System
|
|
457
|
+
| API | Description |
|
|
458
|
+
|-----|-------------|
|
|
459
|
+
| \`GM_setClipboard(text)\` / \`GM.setClipboard(text)\` | Copy text to OS clipboard |
|
|
460
|
+
| \`GM_openInTab(url, options)\` / \`GM.openInTab(url, options)\` | Open a new browser tab |
|
|
461
|
+
| \`GM_getTab(cb)\` / \`GM.getTab()\` | Get persistent state for the current tab |
|
|
462
|
+
| \`GM_saveTab(obj)\` / \`GM.saveTab(obj)\` | Save persistent state for the current tab |
|
|
463
|
+
| \`GM_getTabs(cb)\` / \`GM.getTabs()\` | Get persistent state for all tabs running this script |
|
|
464
|
+
|
|
465
|
+
## Developer Workflow & Best Practices
|
|
466
|
+
|
|
467
|
+
1. **Use \`GM_log\` over \`console.log\`:** \`GM_log\` output is explicitly tracked by Customaise and is visible when using the \`get_console_context\` MCP tool.
|
|
468
|
+
2. **Cross-Origin Requests:** If your script needs to fetch data from \`api.github.com\`, you MUST include \`// @connect api.github.com\` in the metadata block, or \`GM_xmlhttpRequest\` will fail silently.
|
|
469
|
+
3. **Handle Dynamic Pages:** Most modern sites are SPAs (Single Page Applications). Elements may not exist immediately. Use \`VM_findElement\` or \`MutationObserver\` instead of assuming elements are present on load.
|
|
470
|
+
4. **Execution Timing:** \`// @run-at document-idle\` is the safest default as it ensures the initial DOM is fully parsed.
|
|
471
|
+
`;
|
|
472
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@customaise/mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server that connects AI coding agents (Cursor, Claude Code, Windsurf, Antigravity) to the Customaise Chrome extension for userscript management and browser automation.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"customaise-mcp": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist/",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"start": "node dist/index.js",
|
|
17
|
+
"dev": "node --loader ts-node/esm src/index.ts",
|
|
18
|
+
"test": "tsc -p tsconfig.test.json && node --loader ./test-loader.mjs --test test-out/__tests__/*.test.js",
|
|
19
|
+
"prepublishOnly": "npm run build"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"mcp",
|
|
23
|
+
"customaise",
|
|
24
|
+
"userscripts",
|
|
25
|
+
"chrome-extension",
|
|
26
|
+
"browser-automation",
|
|
27
|
+
"model-context-protocol",
|
|
28
|
+
"ai-tools",
|
|
29
|
+
"cursor",
|
|
30
|
+
"claude",
|
|
31
|
+
"windsurf"
|
|
32
|
+
],
|
|
33
|
+
"author": "Customaise",
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"homepage": "https://customaise.com",
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "https://github.com/customaise/customaise-mcp"
|
|
39
|
+
},
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=18.0.0"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
45
|
+
"ws": "^8.18.0",
|
|
46
|
+
"zod": "^3.24.0"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@types/node": "^22.0.0",
|
|
50
|
+
"@types/ws": "^8.5.0",
|
|
51
|
+
"ts-node": "^10.9.0",
|
|
52
|
+
"tsx": "^4.21.0",
|
|
53
|
+
"typescript": "^5.7.0",
|
|
54
|
+
"vitest": "^4.1.0"
|
|
55
|
+
}
|
|
56
|
+
}
|