Skip to content

Commit 95f7fd7

Browse files
committed
Drop desktop MCP support, add server WS MCP support
1 parent d30b74e commit 95f7fd7

3 files changed

Lines changed: 111 additions & 16 deletions

File tree

src/services/desktop-api.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { desktopVersion } from "./service-versions";
2-
import { OperationDefinition } from "./ui-api/api-types";
32

43
type DesktopInjectedKey =
54
| 'httpToolkitDesktopVersion'
@@ -57,11 +56,6 @@ interface DesktopApi {
5756
getPathForFile?: (file: File) => string | null;
5857

5958
setComponentVersions?: (versions: Record<string, string>) => void;
60-
61-
setApiOperations?: (operations: OperationDefinition[]) => void;
62-
onOperationRequest?: (
63-
handler: (operation: string, params: Record<string, unknown>) => Promise<unknown>
64-
) => void;
6559
}
6660

6761
interface NativeContextMenuDefinition {
Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { OperationRegistry } from './api-registry';
22
import { registerAllOperations } from './operations';
3+
import { startServerOperationBridge } from './server-operation-bridge';
34

4-
import { DesktopApi } from '../desktop-api';
55
import { AccountStore } from '../../model/account/account-store';
66
import { EventsStore } from '../../model/events/events-store';
77
import { ProxyStore } from '../../model/proxy-store';
@@ -13,11 +13,6 @@ export function initializeUiApi(stores: {
1313
proxyStore: ProxyStore;
1414
interceptorStore: InterceptorStore;
1515
}) {
16-
if (!DesktopApi.setApiOperations || !DesktopApi.onOperationRequest) {
17-
console.log("UI API not available");
18-
return;
19-
}
20-
2116
const { accountStore, eventsStore, proxyStore, interceptorStore } = stores;
2217

2318
const registry = new OperationRegistry(
@@ -30,8 +25,7 @@ export function initializeUiApi(stores: {
3025
() => eventsStore.events
3126
);
3227

33-
DesktopApi.setApiOperations(registry.getDefinitions());
34-
DesktopApi.onOperationRequest(async (operation, params) => {
35-
return await registry.execute(operation, params || {});
36-
});
28+
// Connect to the server's WebSocket bridge (for MCP and external tool access).
29+
const authToken = new URLSearchParams(window.location.search).get('authToken') ?? undefined;
30+
startServerOperationBridge(registry, authToken);
3731
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import { OperationRegistry } from './api-registry';
2+
3+
const RECONNECT_BASE_MS = 1_000;
4+
const RECONNECT_MAX_MS = 30_000;
5+
6+
export function startServerOperationBridge(
7+
registry: OperationRegistry,
8+
authToken: string | undefined
9+
) {
10+
let ws: WebSocket | null = null;
11+
let reconnectDelay = RECONNECT_BASE_MS;
12+
let reconnectTimer: ReturnType<typeof setTimeout> | null = null;
13+
let destroyed = false;
14+
15+
function connect() {
16+
if (destroyed) return;
17+
18+
const url = authToken
19+
? `ws://127.0.0.1:45457/ui-operations?token=${encodeURIComponent(authToken)}`
20+
: `ws://127.0.0.1:45457/ui-operations`;
21+
22+
try {
23+
ws = new WebSocket(url);
24+
} catch {
25+
scheduleReconnect();
26+
return;
27+
}
28+
29+
ws.onopen = () => {
30+
reconnectDelay = RECONNECT_BASE_MS;
31+
sendOperations();
32+
};
33+
34+
ws.onmessage = (event) => {
35+
try {
36+
const msg = JSON.parse(event.data as string);
37+
handleMessage(msg);
38+
} catch {}
39+
};
40+
41+
ws.onclose = () => {
42+
ws = null;
43+
scheduleReconnect();
44+
};
45+
46+
ws.onerror = () => {
47+
// Error will be followed by close
48+
};
49+
}
50+
51+
function scheduleReconnect() {
52+
if (destroyed || reconnectTimer) return;
53+
reconnectTimer = setTimeout(() => {
54+
reconnectTimer = null;
55+
reconnectDelay = Math.min(reconnectDelay * 2, RECONNECT_MAX_MS);
56+
connect();
57+
}, reconnectDelay);
58+
}
59+
60+
function sendOperations() {
61+
if (!ws || ws.readyState !== WebSocket.OPEN) return;
62+
ws.send(JSON.stringify({
63+
type: 'operations',
64+
operations: registry.getDefinitions()
65+
}));
66+
}
67+
68+
function handleMessage(msg: any) {
69+
if (!msg || msg.type !== 'request') return;
70+
71+
const { id, operation, params } = msg;
72+
73+
registry.execute(operation, params || {}).then((result) => {
74+
if (ws && ws.readyState === WebSocket.OPEN) {
75+
ws.send(JSON.stringify({
76+
type: 'response',
77+
id,
78+
result
79+
}));
80+
}
81+
}).catch((err) => {
82+
if (ws && ws.readyState === WebSocket.OPEN) {
83+
ws.send(JSON.stringify({
84+
type: 'response',
85+
id,
86+
error: err.message || String(err)
87+
}));
88+
}
89+
});
90+
}
91+
92+
connect();
93+
94+
return {
95+
destroy() {
96+
destroyed = true;
97+
if (reconnectTimer) {
98+
clearTimeout(reconnectTimer);
99+
reconnectTimer = null;
100+
}
101+
if (ws) {
102+
ws.close();
103+
ws = null;
104+
}
105+
}
106+
};
107+
}

0 commit comments

Comments
 (0)