mirror of
https://github.com/microsoft/playwright-mcp.git
synced 2025-10-12 00:25:14 +03:00
chore(extension): use react for connect dialog (#777)
This commit is contained in:
@@ -17,18 +17,11 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Playwright MCP extension</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="src/ui/connect.css">
|
||||
</head>
|
||||
<body>
|
||||
<h3>Playwright MCP extension</h3>
|
||||
<div id="status-container"></div>
|
||||
<div class="button-row">
|
||||
<button id="continue-btn">Continue</button>
|
||||
<button id="reject-btn">Reject</button>
|
||||
</div>
|
||||
<div id="tab-list-container">
|
||||
<h4>Select page to expose to MCP server:</h4>
|
||||
<div id="tab-list"></div>
|
||||
</div>
|
||||
<script src="lib/connect.js"></script>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="src/ui/connect.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,172 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
interface TabInfo {
|
||||
id: number;
|
||||
windowId: number;
|
||||
title: string;
|
||||
url: string;
|
||||
favIconUrl?: string;
|
||||
}
|
||||
|
||||
class ConnectPage {
|
||||
private _tabList: HTMLElement;
|
||||
private _tabListContainer: HTMLElement;
|
||||
private _statusContainer: HTMLElement;
|
||||
private _selectedTab: TabInfo | undefined;
|
||||
|
||||
constructor() {
|
||||
this._tabList = document.getElementById('tab-list')!;
|
||||
this._tabListContainer = document.getElementById('tab-list-container')!;
|
||||
this._statusContainer = document.getElementById('status-container') as HTMLElement;
|
||||
this._addButtonHandlers();
|
||||
void this._loadTabs();
|
||||
}
|
||||
|
||||
private _addButtonHandlers() {
|
||||
const continueBtn = document.getElementById('continue-btn') as HTMLButtonElement;
|
||||
const rejectBtn = document.getElementById('reject-btn') as HTMLButtonElement;
|
||||
const buttonRow = document.querySelector('.button-row') as HTMLElement;
|
||||
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const mcpRelayUrl = params.get('mcpRelayUrl');
|
||||
|
||||
if (!mcpRelayUrl) {
|
||||
buttonRow.style.display = 'none';
|
||||
this._showStatus('error', 'Missing mcpRelayUrl parameter in URL.');
|
||||
return;
|
||||
}
|
||||
|
||||
let clientInfo = 'unknown';
|
||||
try {
|
||||
const client = JSON.parse(params.get('client') || '{}');
|
||||
clientInfo = `${client.name}/${client.version}`;
|
||||
} catch (e) {
|
||||
this._showStatus('error', 'Failed to parse client version.');
|
||||
return;
|
||||
}
|
||||
|
||||
this._showStatus('connecting', `MCP client "${clientInfo}" is trying to connect. Do you want to continue?`);
|
||||
|
||||
rejectBtn.addEventListener('click', async () => {
|
||||
buttonRow.style.display = 'none';
|
||||
this._tabListContainer.style.display = 'none';
|
||||
this._showStatus('error', 'Connection rejected. This tab can be closed.');
|
||||
});
|
||||
|
||||
continueBtn.addEventListener('click', async () => {
|
||||
buttonRow.style.display = 'none';
|
||||
this._tabListContainer.style.display = 'none';
|
||||
try {
|
||||
const selectedTab = this._selectedTab;
|
||||
if (!selectedTab) {
|
||||
this._showStatus('error', 'Tab not selected.');
|
||||
return;
|
||||
}
|
||||
const response = await chrome.runtime.sendMessage({
|
||||
type: 'connectToMCPRelay',
|
||||
mcpRelayUrl,
|
||||
tabId: selectedTab.id,
|
||||
windowId: selectedTab.windowId,
|
||||
});
|
||||
if (response?.success)
|
||||
this._showStatus('connected', `MCP client "${clientInfo}" connected.`);
|
||||
else
|
||||
this._showStatus('error', response?.error || `MCP client "${clientInfo}" failed to connect.`);
|
||||
} catch (e) {
|
||||
this._showStatus('error', `MCP client "${clientInfo}" failed to connect: ${e}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async _loadTabs(): Promise<void> {
|
||||
try {
|
||||
const response = await chrome.runtime.sendMessage({ type: 'getTabs' });
|
||||
if (response.success)
|
||||
this._populateTabList(response.tabs, response.currentTabId);
|
||||
else
|
||||
this._showStatus('error', 'Failed to load tabs: ' + response.error);
|
||||
} catch (error) {
|
||||
this._showStatus('error', 'Failed to communicate with background script: ' + error);
|
||||
}
|
||||
}
|
||||
|
||||
private _populateTabList(tabs: TabInfo[], currentTabId: number): void {
|
||||
this._tabList.replaceChildren();
|
||||
this._selectedTab = tabs.find(tab => tab.id === currentTabId);
|
||||
|
||||
tabs.forEach((tab, index) => {
|
||||
const tabElement = this._createTabElement(tab);
|
||||
this._tabList.appendChild(tabElement);
|
||||
});
|
||||
}
|
||||
|
||||
private _createTabElement(tab: TabInfo): HTMLElement {
|
||||
const disabled = tab.url.startsWith('chrome://');
|
||||
|
||||
const tabInfoDiv = document.createElement('div');
|
||||
tabInfoDiv.className = 'tab-info';
|
||||
tabInfoDiv.style.padding = '5px';
|
||||
if (disabled)
|
||||
tabInfoDiv.style.opacity = '0.5';
|
||||
|
||||
const radioButton = document.createElement('input');
|
||||
radioButton.type = 'radio';
|
||||
radioButton.name = 'tab-selection';
|
||||
radioButton.checked = tab.id === this._selectedTab?.id;
|
||||
radioButton.id = `tab-${tab.id}`;
|
||||
radioButton.addEventListener('change', e => {
|
||||
if (radioButton.checked)
|
||||
this._selectedTab = tab;
|
||||
});
|
||||
if (disabled)
|
||||
radioButton.disabled = true;
|
||||
|
||||
const favicon = document.createElement('img');
|
||||
favicon.className = 'tab-favicon';
|
||||
if (tab.favIconUrl)
|
||||
favicon.src = tab.favIconUrl;
|
||||
favicon.alt = '';
|
||||
favicon.style.height = '16px';
|
||||
favicon.style.width = '16px';
|
||||
|
||||
const title = document.createElement('span');
|
||||
title.style.paddingLeft = '5px';
|
||||
title.className = 'tab-title';
|
||||
title.textContent = tab.title || 'Untitled';
|
||||
|
||||
const url = document.createElement('span');
|
||||
url.style.paddingLeft = '5px';
|
||||
url.className = 'tab-url';
|
||||
url.textContent = tab.url;
|
||||
|
||||
tabInfoDiv.appendChild(radioButton);
|
||||
tabInfoDiv.appendChild(favicon);
|
||||
tabInfoDiv.appendChild(title);
|
||||
tabInfoDiv.appendChild(url);
|
||||
|
||||
return tabInfoDiv;
|
||||
}
|
||||
|
||||
private _showStatus(type: 'connected' | 'error' | 'connecting', message: string) {
|
||||
const div = document.createElement('div');
|
||||
div.className = `status ${type}`;
|
||||
div.textContent = message;
|
||||
this._statusContainer.replaceChildren(div);
|
||||
}
|
||||
}
|
||||
|
||||
new ConnectPage();
|
||||
174
extension/src/ui/connect.css
Normal file
174
extension/src/ui/connect.css
Normal file
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
Copyright (c) Microsoft Corporation.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Base styles */
|
||||
.app-container {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif;
|
||||
background-color: #ffffff;
|
||||
color: #1f2328;
|
||||
margin: 0;
|
||||
padding: 24px;
|
||||
min-height: 100vh;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.main-title {
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
color: #1f2328;
|
||||
}
|
||||
|
||||
/* Status Banner */
|
||||
.status-banner {
|
||||
padding: 16px;
|
||||
margin-bottom: 24px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-banner.connected {
|
||||
background-color: #dafbe1;
|
||||
border-color: #1a7f37;
|
||||
color: #0d5a23;
|
||||
}
|
||||
|
||||
.status-banner.error {
|
||||
background-color: #ffebe9;
|
||||
border-color: #da3633;
|
||||
color: #a40e26;
|
||||
}
|
||||
|
||||
.status-banner.connecting {
|
||||
background-color: #fff8c5;
|
||||
border-color: #d1b500;
|
||||
color: #7a5c00;
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.button-container {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.button {
|
||||
padding: 8px 16px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-decoration: none;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.button.primary {
|
||||
background-color: #2da44e;
|
||||
border-color: #2da44e;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.button.primary:hover {
|
||||
background-color: #2c974b;
|
||||
}
|
||||
|
||||
.button.default {
|
||||
background-color: #f6f8fa;
|
||||
border-color: #d1d9e0;
|
||||
color: #24292f;
|
||||
}
|
||||
|
||||
.button.default:hover {
|
||||
background-color: #f3f4f6;
|
||||
border-color: #c7d2da;
|
||||
}
|
||||
|
||||
/* Tab selection */
|
||||
.tab-section-title {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 16px;
|
||||
color: #1f2328;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px;
|
||||
border: 1px solid #d1d9e0;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 8px;
|
||||
background-color: #ffffff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tab-item.selected {
|
||||
background-color: #f6f8fa;
|
||||
}
|
||||
|
||||
.tab-item.disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.tab-radio {
|
||||
margin-right: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tab-favicon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-right: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.tab-title {
|
||||
font-weight: 500;
|
||||
color: #1f2328;
|
||||
margin-bottom: 2px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.tab-url {
|
||||
font-size: 12px;
|
||||
color: #656d76;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
213
extension/src/ui/connect.tsx
Normal file
213
extension/src/ui/connect.tsx
Normal file
@@ -0,0 +1,213 @@
|
||||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import './connect.css';
|
||||
|
||||
interface TabInfo {
|
||||
id: number;
|
||||
windowId: number;
|
||||
title: string;
|
||||
url: string;
|
||||
favIconUrl?: string;
|
||||
}
|
||||
|
||||
type StatusType = 'connected' | 'error' | 'connecting';
|
||||
|
||||
const ConnectApp: React.FC = () => {
|
||||
const [tabs, setTabs] = useState<TabInfo[]>([]);
|
||||
const [selectedTab, setSelectedTab] = useState<TabInfo | undefined>();
|
||||
const [status, setStatus] = useState<{ type: StatusType; message: string } | null>(null);
|
||||
const [showButtons, setShowButtons] = useState(true);
|
||||
const [showTabList, setShowTabList] = useState(true);
|
||||
const [clientInfo, setClientInfo] = useState('unknown');
|
||||
const [mcpRelayUrl, setMcpRelayUrl] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const relayUrl = params.get('mcpRelayUrl');
|
||||
|
||||
if (!relayUrl) {
|
||||
setShowButtons(false);
|
||||
setStatus({ type: 'error', message: 'Missing mcpRelayUrl parameter in URL.' });
|
||||
return;
|
||||
}
|
||||
|
||||
setMcpRelayUrl(relayUrl);
|
||||
|
||||
try {
|
||||
const client = JSON.parse(params.get('client') || '{}');
|
||||
const info = `${client.name}/${client.version}`;
|
||||
setClientInfo(info);
|
||||
setStatus({
|
||||
type: 'connecting',
|
||||
message: `MCP client "${info}" is trying to connect. Do you want to continue?`
|
||||
});
|
||||
} catch (e) {
|
||||
setStatus({ type: 'error', message: 'Failed to parse client version.' });
|
||||
return;
|
||||
}
|
||||
|
||||
void loadTabs();
|
||||
}, []);
|
||||
|
||||
const loadTabs = async () => {
|
||||
const response = await chrome.runtime.sendMessage({ type: 'getTabs' });
|
||||
if (response.success) {
|
||||
setTabs(response.tabs);
|
||||
const currentTab = response.tabs.find((tab: TabInfo) => tab.id === response.currentTabId);
|
||||
setSelectedTab(currentTab);
|
||||
} else {
|
||||
setStatus({ type: 'error', message: 'Failed to load tabs: ' + response.error });
|
||||
}
|
||||
};
|
||||
|
||||
const handleContinue = useCallback(async () => {
|
||||
setShowButtons(false);
|
||||
setShowTabList(false);
|
||||
|
||||
if (!selectedTab) {
|
||||
setStatus({ type: 'error', message: 'Tab not selected.' });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await chrome.runtime.sendMessage({
|
||||
type: 'connectToMCPRelay',
|
||||
mcpRelayUrl,
|
||||
tabId: selectedTab.id,
|
||||
windowId: selectedTab.windowId,
|
||||
});
|
||||
|
||||
if (response?.success) {
|
||||
setStatus({ type: 'connected', message: `MCP client "${clientInfo}" connected.` });
|
||||
} else {
|
||||
setStatus({
|
||||
type: 'error',
|
||||
message: response?.error || `MCP client "${clientInfo}" failed to connect.`
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
setStatus({
|
||||
type: 'error',
|
||||
message: `MCP client "${clientInfo}" failed to connect: ${e}`
|
||||
});
|
||||
}
|
||||
}, [selectedTab, clientInfo, mcpRelayUrl]);
|
||||
|
||||
const handleReject = useCallback(() => {
|
||||
setShowButtons(false);
|
||||
setShowTabList(false);
|
||||
setStatus({ type: 'error', message: 'Connection rejected. This tab can be closed.' });
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className='app-container'>
|
||||
<div className='content-wrapper'>
|
||||
<h1 className='main-title'>
|
||||
Playwright MCP Extension
|
||||
</h1>
|
||||
|
||||
{status && <StatusBanner type={status.type} message={status.message} />}
|
||||
|
||||
{showButtons && (
|
||||
<div className='button-container'>
|
||||
<Button variant='primary' onClick={handleContinue}>
|
||||
Continue
|
||||
</Button>
|
||||
<Button variant='default' onClick={handleReject}>
|
||||
Reject
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
{showTabList && (
|
||||
<div>
|
||||
<h2 className='tab-section-title'>
|
||||
Select page to expose to MCP server:
|
||||
</h2>
|
||||
<div>
|
||||
{tabs.map(tab => (
|
||||
<TabItem
|
||||
key={tab.id}
|
||||
tab={tab}
|
||||
isSelected={selectedTab?.id === tab.id}
|
||||
onSelect={() => setSelectedTab(tab)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const StatusBanner: React.FC<{ type: StatusType; message: string }> = ({ type, message }) => {
|
||||
return <div className={`status-banner ${type}`}>{message}</div>;
|
||||
};
|
||||
|
||||
const Button: React.FC<{ variant: 'primary' | 'default'; onClick: () => void; children: React.ReactNode }> = ({
|
||||
variant,
|
||||
onClick,
|
||||
children
|
||||
}) => {
|
||||
return (
|
||||
<button className={`button ${variant}`} onClick={onClick}>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
const TabItem: React.FC<{ tab: TabInfo; isSelected: boolean; onSelect: () => void }> = ({
|
||||
tab,
|
||||
isSelected,
|
||||
onSelect
|
||||
}) => {
|
||||
const disabled = tab.url.startsWith('chrome://');
|
||||
|
||||
const className = `tab-item ${isSelected ? 'selected' : ''} ${disabled ? 'disabled' : ''}`.trim();
|
||||
|
||||
return (
|
||||
<div className={className} onClick={disabled ? undefined : onSelect}>
|
||||
<input
|
||||
type='radio'
|
||||
className='tab-radio'
|
||||
checked={isSelected}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<img
|
||||
src={tab.favIconUrl || 'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><rect width="16" height="16" fill="%23f6f8fa"/></svg>'}
|
||||
alt=''
|
||||
className='tab-favicon'
|
||||
/>
|
||||
<div className='tab-content'>
|
||||
<div className='tab-title'>{tab.title || 'Untitled'}</div>
|
||||
<div className='tab-url'>{tab.url}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
// Initialize the React app
|
||||
const container = document.getElementById('root');
|
||||
if (container) {
|
||||
const root = createRoot(container);
|
||||
root.render(<ConnectApp />);
|
||||
}
|
||||
@@ -8,8 +8,13 @@
|
||||
"rootDir": "src",
|
||||
"outDir": "./lib",
|
||||
"resolveJsonModule": true,
|
||||
"jsx": "react-jsx",
|
||||
"jsxImportSource": "react"
|
||||
},
|
||||
"include": [
|
||||
"src",
|
||||
],
|
||||
"exclude": [
|
||||
"src/ui",
|
||||
]
|
||||
}
|
||||
|
||||
18
extension/tsconfig.ui.json
Normal file
18
extension/tsconfig.ui.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"esModuleInterop": true,
|
||||
"moduleResolution": "node",
|
||||
"strict": true,
|
||||
"module": "ESNext",
|
||||
"rootDir": "src",
|
||||
"outDir": "./lib",
|
||||
"resolveJsonModule": true,
|
||||
"jsx": "react-jsx",
|
||||
"jsxImportSource": "react",
|
||||
"noEmit": true,
|
||||
},
|
||||
"include": [
|
||||
"src/ui",
|
||||
],
|
||||
}
|
||||
40
extension/vite.config.ts
Normal file
40
extension/vite.config.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { resolve } from 'path';
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
base: '/lib/ui/',
|
||||
build: {
|
||||
outDir: resolve(__dirname, 'lib/ui'),
|
||||
emptyOutDir: true,
|
||||
minify: false,
|
||||
rollupOptions: {
|
||||
input: resolve(__dirname, 'connect.html'),
|
||||
output: {
|
||||
manualChunks: undefined,
|
||||
inlineDynamicImports: true,
|
||||
entryFileNames: '[name].js',
|
||||
chunkFileNames: '[name].js',
|
||||
assetFileNames: '[name].[ext]'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
2034
package-lock.json
generated
2034
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
12
package.json
12
package.json
@@ -17,12 +17,13 @@
|
||||
"license": "Apache-2.0",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"build:extension": "tsc --project extension",
|
||||
"build:extension": "tsc --project extension && tsc --project extension && vite build extension",
|
||||
"lint": "npm run update-readme && eslint . && tsc --noEmit",
|
||||
"lint-fix": "eslint . --fix",
|
||||
"update-readme": "node utils/update-readme.js",
|
||||
"watch": "tsc --watch",
|
||||
"watch:extension": "tsc --watch --project extension",
|
||||
"watch:extension": "tsc --watch --project extension & tsc --watch --project extension/tsconfig.ui.json & vite build extension --watch",
|
||||
"dev:extension": "vite build --watch",
|
||||
"test": "playwright test",
|
||||
"ctest": "playwright test --project=chrome",
|
||||
"ftest": "playwright test --project=firefox",
|
||||
@@ -58,14 +59,21 @@
|
||||
"@types/chrome": "^0.0.315",
|
||||
"@types/debug": "^4.1.12",
|
||||
"@types/node": "^22.13.10",
|
||||
"@types/react": "^18.2.66",
|
||||
"@types/react-dom": "^18.2.22",
|
||||
"@types/ws": "^8.18.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.26.1",
|
||||
"@typescript-eslint/parser": "^8.26.1",
|
||||
"@typescript-eslint/utils": "^8.26.1",
|
||||
"esbuild": "^0.20.1",
|
||||
"eslint": "^9.19.0",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"vite": "^5.0.0",
|
||||
"@vitejs/plugin-react": "^4.0.0",
|
||||
"eslint-plugin-notice": "^1.0.0",
|
||||
"openai": "^5.10.2",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"typescript": "^5.8.2"
|
||||
},
|
||||
"bin": {
|
||||
|
||||
@@ -103,7 +103,7 @@ export class CDPRelayServer {
|
||||
private async _connectBrowser(clientInfo: { name: string, version: string }) {
|
||||
const mcpRelayEndpoint = `${this._wsHost}${this._extensionPath}`;
|
||||
// Need to specify "key" in the manifest.json to make the id stable when loading from file.
|
||||
const url = new URL('chrome-extension://jakfalbnbhgkpmoaakfflhflbfpkailf/connect.html');
|
||||
const url = new URL('chrome-extension://jakfalbnbhgkpmoaakfflhflbfpkailf/lib/ui/connect.html');
|
||||
url.searchParams.set('mcpRelayUrl', mcpRelayEndpoint);
|
||||
url.searchParams.set('client', JSON.stringify(clientInfo));
|
||||
const href = url.toString();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"include": ["**/*.ts", "**/*.js"],
|
||||
"include": ["**/*.ts", "**/*.tsx", "**/*.js"],
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user