@wonderwhy-er/desktop-commander 0.2.29-alpha.1 → 0.2.29-alpha.3

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.
Files changed (53) hide show
  1. package/dist/remote-client.d.ts +44 -0
  2. package/dist/remote-client.js +174 -0
  3. package/dist/remote-device/device-authenticator.js +2 -8
  4. package/dist/remote-device/templates/auth-success.d.ts +1 -0
  5. package/dist/remote-device/templates/auth-success.js +30 -0
  6. package/dist/remote-sse-client.d.ts +45 -0
  7. package/dist/remote-sse-client.js +243 -0
  8. package/dist/tools/remote-mcp.d.ts +20 -0
  9. package/dist/tools/remote-mcp.js +149 -0
  10. package/dist/version.d.ts +1 -1
  11. package/dist/version.js +1 -1
  12. package/package.json +3 -1
  13. package/dist/data/spec-kit-prompts.json +0 -123
  14. package/dist/handlers/node-handlers.d.ts +0 -6
  15. package/dist/handlers/node-handlers.js +0 -73
  16. package/dist/handlers/test-crash-handler.d.ts +0 -11
  17. package/dist/handlers/test-crash-handler.js +0 -26
  18. package/dist/http-index.d.ts +0 -45
  19. package/dist/http-index.js +0 -51
  20. package/dist/http-server-auto-tunnel.d.ts +0 -1
  21. package/dist/http-server-auto-tunnel.js +0 -667
  22. package/dist/http-server-named-tunnel.d.ts +0 -2
  23. package/dist/http-server-named-tunnel.js +0 -167
  24. package/dist/http-server-tunnel.d.ts +0 -2
  25. package/dist/http-server-tunnel.js +0 -111
  26. package/dist/http-server.d.ts +0 -2
  27. package/dist/http-server.js +0 -270
  28. package/dist/index-oauth.d.ts +0 -2
  29. package/dist/index-oauth.js +0 -201
  30. package/dist/oauth/auth-middleware.d.ts +0 -20
  31. package/dist/oauth/auth-middleware.js +0 -62
  32. package/dist/oauth/index.d.ts +0 -3
  33. package/dist/oauth/index.js +0 -3
  34. package/dist/oauth/oauth-manager.d.ts +0 -80
  35. package/dist/oauth/oauth-manager.js +0 -179
  36. package/dist/oauth/oauth-routes.d.ts +0 -3
  37. package/dist/oauth/oauth-routes.js +0 -377
  38. package/dist/oauth/provider.d.ts +0 -22
  39. package/dist/oauth/provider.js +0 -124
  40. package/dist/oauth/server.d.ts +0 -18
  41. package/dist/oauth/server.js +0 -160
  42. package/dist/oauth/types.d.ts +0 -54
  43. package/dist/oauth/types.js +0 -2
  44. package/dist/setup.log +0 -275
  45. package/dist/test-setup.js +0 -14
  46. package/dist/tools/pdf-processor.d.ts +0 -1
  47. package/dist/tools/pdf-processor.js +0 -3
  48. package/dist/tools/search.d.ts +0 -32
  49. package/dist/tools/search.js +0 -202
  50. package/dist/utils/crash-logger.d.ts +0 -18
  51. package/dist/utils/crash-logger.js +0 -44
  52. package/dist/utils/dedent.d.ts +0 -8
  53. package/dist/utils/dedent.js +0 -38
@@ -0,0 +1,44 @@
1
+ export interface RemoteClientConfig {
2
+ serverUrl: string;
3
+ deviceToken: string;
4
+ retryInterval?: number;
5
+ maxRetries?: number;
6
+ }
7
+ export interface MCPRequest {
8
+ jsonrpc: string;
9
+ id: string | number;
10
+ method: string;
11
+ params?: any;
12
+ }
13
+ export interface MCPResponse {
14
+ jsonrpc: string;
15
+ id: string | number;
16
+ result?: any;
17
+ error?: {
18
+ code: number;
19
+ message: string;
20
+ data?: any;
21
+ };
22
+ }
23
+ export declare class RemoteClient {
24
+ private ws;
25
+ private config;
26
+ private isAuthenticated;
27
+ private deviceId;
28
+ private pendingRequests;
29
+ private reconnectAttempts;
30
+ private reconnectTimer;
31
+ constructor(config: RemoteClientConfig);
32
+ connect(): Promise<void>;
33
+ private handleMessage;
34
+ private handleReconnect;
35
+ private sendMessage;
36
+ sendMCPRequest(request: MCPRequest): Promise<MCPResponse>;
37
+ isConnected(): boolean;
38
+ disconnect(): void;
39
+ getStatus(): {
40
+ connected: boolean;
41
+ authenticated: boolean;
42
+ deviceId: string | null;
43
+ };
44
+ }
@@ -0,0 +1,174 @@
1
+ import WebSocket from 'ws';
2
+ import { logger } from './utils/logger.js';
3
+ export class RemoteClient {
4
+ constructor(config) {
5
+ this.ws = null;
6
+ this.isAuthenticated = false;
7
+ this.deviceId = null;
8
+ this.pendingRequests = new Map();
9
+ this.reconnectAttempts = 0;
10
+ this.reconnectTimer = null;
11
+ this.config = {
12
+ retryInterval: 5000,
13
+ maxRetries: 5,
14
+ ...config
15
+ };
16
+ }
17
+ async connect() {
18
+ return new Promise((resolve, reject) => {
19
+ try {
20
+ logger.info(`Connecting to Remote MCP Server: ${this.config.serverUrl}`);
21
+ this.ws = new WebSocket(this.config.serverUrl);
22
+ this.ws.on('open', () => {
23
+ logger.info('Connected to Remote MCP Server');
24
+ this.reconnectAttempts = 0;
25
+ // Send authentication
26
+ this.sendMessage({
27
+ id: 'auth-1',
28
+ type: 'auth',
29
+ payload: { deviceToken: this.config.deviceToken },
30
+ timestamp: Date.now()
31
+ });
32
+ });
33
+ this.ws.on('message', (data) => {
34
+ try {
35
+ const message = JSON.parse(data.toString());
36
+ this.handleMessage(message);
37
+ // Resolve connection promise on successful auth
38
+ if (message.type === 'auth' && message.payload?.success && !this.isAuthenticated) {
39
+ this.isAuthenticated = true;
40
+ this.deviceId = message.payload.deviceId;
41
+ logger.info(`Remote MCP authentication successful! Device ID: ${this.deviceId}`);
42
+ resolve();
43
+ }
44
+ }
45
+ catch (error) {
46
+ logger.error('Error processing remote message:', error);
47
+ }
48
+ });
49
+ this.ws.on('close', (code, reason) => {
50
+ logger.info(`Remote MCP connection closed (${code}): ${reason}`);
51
+ this.isAuthenticated = false;
52
+ this.deviceId = null;
53
+ this.handleReconnect();
54
+ });
55
+ this.ws.on('error', (error) => {
56
+ logger.error('Remote MCP WebSocket error:', error.message);
57
+ if (!this.isAuthenticated) {
58
+ reject(new Error(`Failed to connect to Remote MCP Server: ${error.message}`));
59
+ }
60
+ });
61
+ // Set up heartbeat
62
+ setInterval(() => {
63
+ if (this.isConnected()) {
64
+ this.sendMessage({
65
+ id: `heartbeat-${Date.now()}`,
66
+ type: 'heartbeat',
67
+ payload: { timestamp: Date.now() },
68
+ timestamp: Date.now()
69
+ });
70
+ }
71
+ }, 30000);
72
+ }
73
+ catch (error) {
74
+ reject(error);
75
+ }
76
+ });
77
+ }
78
+ handleMessage(message) {
79
+ if (message.type === 'mcp_response') {
80
+ // Handle MCP response
81
+ const request = this.pendingRequests.get(message.payload.id);
82
+ if (request) {
83
+ clearTimeout(request.timeout);
84
+ this.pendingRequests.delete(message.payload.id);
85
+ request.resolve(message.payload);
86
+ }
87
+ }
88
+ else if (message.type === 'heartbeat') {
89
+ // Respond to heartbeat
90
+ this.sendMessage({
91
+ id: message.id,
92
+ type: 'heartbeat',
93
+ payload: { timestamp: Date.now() },
94
+ timestamp: Date.now()
95
+ });
96
+ }
97
+ }
98
+ handleReconnect() {
99
+ if (this.reconnectAttempts >= (this.config.maxRetries || 5)) {
100
+ logger.error('Max reconnection attempts reached. Giving up.');
101
+ return;
102
+ }
103
+ this.reconnectAttempts++;
104
+ const delay = (this.config.retryInterval || 5000) * this.reconnectAttempts;
105
+ logger.info(`Attempting to reconnect in ${delay}ms (attempt ${this.reconnectAttempts})`);
106
+ this.reconnectTimer = setTimeout(async () => {
107
+ try {
108
+ await this.connect();
109
+ }
110
+ catch (error) {
111
+ logger.error('Reconnection failed:', error);
112
+ this.handleReconnect();
113
+ }
114
+ }, delay);
115
+ }
116
+ sendMessage(message) {
117
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
118
+ this.ws.send(JSON.stringify(message));
119
+ }
120
+ }
121
+ async sendMCPRequest(request) {
122
+ if (!this.isConnected() || !this.isAuthenticated) {
123
+ throw new Error('Remote MCP client not connected or authenticated');
124
+ }
125
+ return new Promise((resolve, reject) => {
126
+ const requestId = `mcp-${Date.now()}-${Math.random()}`;
127
+ // Set up timeout
128
+ const timeout = setTimeout(() => {
129
+ this.pendingRequests.delete(request.id);
130
+ reject(new Error('Remote MCP request timeout'));
131
+ }, 30000); // 30 second timeout
132
+ this.pendingRequests.set(request.id, {
133
+ resolve,
134
+ reject,
135
+ timeout
136
+ });
137
+ // Send MCP request to remote server
138
+ this.sendMessage({
139
+ id: requestId,
140
+ type: 'mcp_request',
141
+ payload: request,
142
+ timestamp: Date.now()
143
+ });
144
+ });
145
+ }
146
+ isConnected() {
147
+ return this.ws !== null && this.ws.readyState === WebSocket.OPEN && this.isAuthenticated;
148
+ }
149
+ disconnect() {
150
+ if (this.reconnectTimer) {
151
+ clearTimeout(this.reconnectTimer);
152
+ this.reconnectTimer = null;
153
+ }
154
+ // Clear pending requests
155
+ this.pendingRequests.forEach(({ timeout, reject }) => {
156
+ clearTimeout(timeout);
157
+ reject(new Error('Client disconnecting'));
158
+ });
159
+ this.pendingRequests.clear();
160
+ if (this.ws) {
161
+ this.ws.close();
162
+ this.ws = null;
163
+ }
164
+ this.isAuthenticated = false;
165
+ this.deviceId = null;
166
+ }
167
+ getStatus() {
168
+ return {
169
+ connected: this.ws?.readyState === WebSocket.OPEN || false,
170
+ authenticated: this.isAuthenticated,
171
+ deviceId: this.deviceId
172
+ };
173
+ }
174
+ }
@@ -2,11 +2,7 @@ import express from 'express';
2
2
  import { createServer } from 'http';
3
3
  import open from 'open';
4
4
  import readline from 'readline';
5
- import fs from 'fs';
6
- import path from 'path';
7
- import { fileURLToPath } from 'url';
8
- const __filename = fileURLToPath(import.meta.url);
9
- const __dirname = path.dirname(__filename);
5
+ import { authSuccessHtml } from './templates/auth-success.js';
10
6
  function escapeHtml(text) {
11
7
  if (text === null || text === undefined)
12
8
  return '';
@@ -62,9 +58,7 @@ export class DeviceAuthenticator {
62
58
  reject(new Error(`${error}: ${error_description}`));
63
59
  }
64
60
  else if (token) {
65
- const templatePath = path.join(__dirname, 'templates', 'auth-success.html');
66
- const htmlContent = fs.readFileSync(templatePath, 'utf8');
67
- res.send(htmlContent);
61
+ res.send(authSuccessHtml);
68
62
  server.close();
69
63
  console.log(' - ✅ Authentication successful, token received');
70
64
  resolve({
@@ -0,0 +1 @@
1
+ export declare const authSuccessHtml = "\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Authentication Successful</title>\n</head>\n\n<body\n style=\"margin: 0; font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; line-height: 1.5; color: #F8FAFC; background-color: #101219; min-height: 100vh; display: flex; align-items: center; justify-content: center;\">\n <div\n style=\"background: rgba(30, 41, 59, 0.4); backdrop-filter: blur(12px); border: 1px solid #2B303B; border-radius: 12px; padding: 40px; width: 100%; max-width: 480px; margin: 20px; text-align: center; box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);\">\n <h2 style=\"font-size: 24px; font-weight: 700; color: #F8FAFC; margin-top: 0; margin-bottom: 16px;\">\n Authentication Successful!</h2>\n <p style=\"color: #94A3B8; font-size: 15px; line-height: 1.6; margin: 0 0 8px 0;\">Your device is now connected.\n </p>\n <p style=\"color: #94A3B8; font-size: 15px; line-height: 1.6; margin: 0;\">You can close this window.</p>\n </div>\n <script>\n // Clean up URL parameters and hash\n if (window.history && window.history.replaceState) {\n window.history.replaceState(null, '', window.location.pathname);\n }\n </script>\n</body>\n\n</html>\n";
@@ -0,0 +1,30 @@
1
+ export const authSuccessHtml = `
2
+ <!DOCTYPE html>
3
+ <html lang="en">
4
+
5
+ <head>
6
+ <meta charset="UTF-8">
7
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
8
+ <title>Authentication Successful</title>
9
+ </head>
10
+
11
+ <body
12
+ style="margin: 0; font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; line-height: 1.5; color: #F8FAFC; background-color: #101219; min-height: 100vh; display: flex; align-items: center; justify-content: center;">
13
+ <div
14
+ style="background: rgba(30, 41, 59, 0.4); backdrop-filter: blur(12px); border: 1px solid #2B303B; border-radius: 12px; padding: 40px; width: 100%; max-width: 480px; margin: 20px; text-align: center; box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);">
15
+ <h2 style="font-size: 24px; font-weight: 700; color: #F8FAFC; margin-top: 0; margin-bottom: 16px;">
16
+ Authentication Successful!</h2>
17
+ <p style="color: #94A3B8; font-size: 15px; line-height: 1.6; margin: 0 0 8px 0;">Your device is now connected.
18
+ </p>
19
+ <p style="color: #94A3B8; font-size: 15px; line-height: 1.6; margin: 0;">You can close this window.</p>
20
+ </div>
21
+ <script>
22
+ // Clean up URL parameters and hash
23
+ if (window.history && window.history.replaceState) {
24
+ window.history.replaceState(null, '', window.location.pathname);
25
+ }
26
+ </script>
27
+ </body>
28
+
29
+ </html>
30
+ `;
@@ -0,0 +1,45 @@
1
+ export interface RemoteSSEClientConfig {
2
+ serverUrl: string;
3
+ deviceToken: string;
4
+ retryInterval?: number;
5
+ maxRetries?: number;
6
+ }
7
+ export interface MCPRequest {
8
+ jsonrpc: string;
9
+ id: string | number;
10
+ method: string;
11
+ params?: any;
12
+ }
13
+ export interface MCPResponse {
14
+ jsonrpc: string;
15
+ id: string | number;
16
+ result?: any;
17
+ error?: {
18
+ code: number;
19
+ message: string;
20
+ data?: any;
21
+ };
22
+ }
23
+ export declare class RemoteSSEClient {
24
+ private eventSource;
25
+ private config;
26
+ private isConnected;
27
+ private isAuthenticated;
28
+ private deviceId;
29
+ private pendingRequests;
30
+ private reconnectAttempts;
31
+ private reconnectTimer;
32
+ constructor(config: RemoteSSEClientConfig);
33
+ connect(): Promise<void>;
34
+ private connectWithFetch;
35
+ private handleSSEMessage;
36
+ sendMCPRequest(request: MCPRequest): Promise<MCPResponse>;
37
+ private handleReconnect;
38
+ isConnectedAndAuthenticated(): boolean;
39
+ disconnect(): void;
40
+ getStatus(): {
41
+ connected: boolean;
42
+ authenticated: boolean;
43
+ deviceId: string | null;
44
+ };
45
+ }
@@ -0,0 +1,243 @@
1
+ import { logger } from './utils/logger.js';
2
+ export class RemoteSSEClient {
3
+ constructor(config) {
4
+ this.eventSource = null;
5
+ this.isConnected = false;
6
+ this.isAuthenticated = false;
7
+ this.deviceId = null;
8
+ this.pendingRequests = new Map();
9
+ this.reconnectAttempts = 0;
10
+ this.reconnectTimer = null;
11
+ this.config = {
12
+ retryInterval: 5000,
13
+ maxRetries: 5,
14
+ ...config
15
+ };
16
+ }
17
+ async connect() {
18
+ return new Promise((resolve, reject) => {
19
+ try {
20
+ const sseUrl = `${this.config.serverUrl}/sse?deviceToken=${encodeURIComponent(this.config.deviceToken)}`;
21
+ logger.info(`Connecting to Remote MCP Server via SSE: ${sseUrl}`);
22
+ // Check if EventSource is available (Node.js environment needs polyfill)
23
+ if (typeof EventSource === 'undefined') {
24
+ // For Node.js environment, we'll use a different approach
25
+ this.connectWithFetch(sseUrl, resolve, reject);
26
+ return;
27
+ }
28
+ this.eventSource = new EventSource(sseUrl);
29
+ this.eventSource.onopen = () => {
30
+ logger.info('SSE connection opened');
31
+ this.isConnected = true;
32
+ this.reconnectAttempts = 0;
33
+ };
34
+ this.eventSource.onmessage = (event) => {
35
+ this.handleSSEMessage('message', event.data);
36
+ };
37
+ this.eventSource.addEventListener('connected', (event) => {
38
+ this.handleSSEMessage('connected', event.data);
39
+ this.isAuthenticated = true;
40
+ resolve();
41
+ });
42
+ this.eventSource.addEventListener('mcp_response', (event) => {
43
+ this.handleSSEMessage('mcp_response', event.data);
44
+ });
45
+ this.eventSource.addEventListener('heartbeat', (event) => {
46
+ this.handleSSEMessage('heartbeat', event.data);
47
+ });
48
+ this.eventSource.onerror = (event) => {
49
+ logger.error('SSE connection error:', event);
50
+ if (!this.isAuthenticated) {
51
+ reject(new Error('Failed to connect to Remote MCP Server via SSE'));
52
+ }
53
+ else {
54
+ this.handleReconnect();
55
+ }
56
+ };
57
+ }
58
+ catch (error) {
59
+ reject(error);
60
+ }
61
+ });
62
+ }
63
+ async connectWithFetch(sseUrl, resolve, reject) {
64
+ try {
65
+ // For Node.js environment, use fetch with streaming
66
+ const fetch = (await import('cross-fetch')).default;
67
+ const response = await fetch(sseUrl, {
68
+ headers: {
69
+ 'Accept': 'text/event-stream',
70
+ 'Cache-Control': 'no-cache',
71
+ },
72
+ });
73
+ if (!response.ok) {
74
+ throw new Error(`SSE connection failed: ${response.status} ${response.statusText}`);
75
+ }
76
+ this.isConnected = true;
77
+ this.reconnectAttempts = 0;
78
+ logger.info('SSE connection established via fetch');
79
+ // Handle the readable stream
80
+ const reader = response.body?.getReader();
81
+ if (!reader) {
82
+ throw new Error('No readable stream available');
83
+ }
84
+ const decoder = new TextDecoder();
85
+ let buffer = '';
86
+ // Process the stream
87
+ const processStream = async () => {
88
+ while (this.isConnected) {
89
+ try {
90
+ const { done, value } = await reader.read();
91
+ if (done)
92
+ break;
93
+ buffer += decoder.decode(value, { stream: true });
94
+ // Process complete SSE messages
95
+ const lines = buffer.split('\n');
96
+ buffer = lines.pop() || ''; // Keep incomplete line in buffer
97
+ let eventType = 'message';
98
+ let eventData = '';
99
+ for (const line of lines) {
100
+ if (line.startsWith('event: ')) {
101
+ eventType = line.substring(7);
102
+ }
103
+ else if (line.startsWith('data: ')) {
104
+ eventData = line.substring(6);
105
+ }
106
+ else if (line === '' && eventData) {
107
+ // Complete event received
108
+ this.handleSSEMessage(eventType, eventData);
109
+ // Resolve on first successful connection
110
+ if (eventType === 'connected' && !this.isAuthenticated) {
111
+ this.isAuthenticated = true;
112
+ resolve();
113
+ }
114
+ eventType = 'message';
115
+ eventData = '';
116
+ }
117
+ }
118
+ }
119
+ catch (error) {
120
+ logger.error('Error reading SSE stream:', error);
121
+ break;
122
+ }
123
+ }
124
+ };
125
+ processStream().catch(error => {
126
+ logger.error('SSE stream processing error:', error);
127
+ if (!this.isAuthenticated) {
128
+ reject(error);
129
+ }
130
+ else {
131
+ this.handleReconnect();
132
+ }
133
+ });
134
+ }
135
+ catch (error) {
136
+ reject(error);
137
+ }
138
+ }
139
+ handleSSEMessage(eventType, data) {
140
+ try {
141
+ const message = JSON.parse(data);
142
+ switch (eventType) {
143
+ case 'connected':
144
+ this.deviceId = message.deviceId;
145
+ logger.info(`Remote MCP SSE authentication successful! Device ID: ${this.deviceId}`);
146
+ break;
147
+ case 'mcp_response':
148
+ // This shouldn't happen in our architecture since the local agent sends responses directly
149
+ // But we can handle it for completeness
150
+ logger.info('Received unexpected mcp_response via SSE');
151
+ break;
152
+ case 'heartbeat':
153
+ // Silent heartbeat handling
154
+ break;
155
+ default:
156
+ logger.info(`Received SSE event: ${eventType}`, message);
157
+ }
158
+ }
159
+ catch (error) {
160
+ logger.error('Error processing SSE message:', error);
161
+ }
162
+ }
163
+ async sendMCPRequest(request) {
164
+ if (!this.isConnected || !this.isAuthenticated) {
165
+ throw new Error('Remote MCP SSE client not connected or authenticated');
166
+ }
167
+ // In the SSE architecture, we don't send requests directly via SSE
168
+ // Instead, we send them via HTTP POST to the server's MCP endpoint
169
+ return new Promise(async (resolve, reject) => {
170
+ try {
171
+ const fetch = (await import('cross-fetch')).default;
172
+ const response = await fetch(`${this.config.serverUrl}/api/mcp/execute`, {
173
+ method: 'POST',
174
+ headers: {
175
+ 'Content-Type': 'application/json',
176
+ 'Authorization': `Bearer ${this.config.deviceToken}`
177
+ },
178
+ body: JSON.stringify({
179
+ deviceToken: this.config.deviceToken,
180
+ request: request
181
+ })
182
+ });
183
+ if (!response.ok) {
184
+ throw new Error(`MCP request failed: ${response.status} ${response.statusText}`);
185
+ }
186
+ const result = await response.json();
187
+ resolve(result);
188
+ }
189
+ catch (error) {
190
+ reject(error);
191
+ }
192
+ });
193
+ }
194
+ handleReconnect() {
195
+ if (this.reconnectAttempts >= (this.config.maxRetries || 5)) {
196
+ logger.error('Max SSE reconnection attempts reached. Giving up.');
197
+ return;
198
+ }
199
+ this.isConnected = false;
200
+ this.isAuthenticated = false;
201
+ this.reconnectAttempts++;
202
+ const delay = (this.config.retryInterval || 5000) * this.reconnectAttempts;
203
+ logger.info(`Attempting to reconnect SSE in ${delay}ms (attempt ${this.reconnectAttempts})`);
204
+ this.reconnectTimer = setTimeout(async () => {
205
+ try {
206
+ await this.connect();
207
+ }
208
+ catch (error) {
209
+ logger.error('SSE reconnection failed:', error);
210
+ this.handleReconnect();
211
+ }
212
+ }, delay);
213
+ }
214
+ isConnectedAndAuthenticated() {
215
+ return this.isConnected && this.isAuthenticated;
216
+ }
217
+ disconnect() {
218
+ if (this.reconnectTimer) {
219
+ clearTimeout(this.reconnectTimer);
220
+ this.reconnectTimer = null;
221
+ }
222
+ // Clear pending requests
223
+ this.pendingRequests.forEach(({ timeout, reject }) => {
224
+ clearTimeout(timeout);
225
+ reject(new Error('Client disconnecting'));
226
+ });
227
+ this.pendingRequests.clear();
228
+ if (this.eventSource) {
229
+ this.eventSource.close();
230
+ this.eventSource = null;
231
+ }
232
+ this.isConnected = false;
233
+ this.isAuthenticated = false;
234
+ this.deviceId = null;
235
+ }
236
+ getStatus() {
237
+ return {
238
+ connected: this.isConnected,
239
+ authenticated: this.isAuthenticated,
240
+ deviceId: this.deviceId
241
+ };
242
+ }
243
+ }
@@ -0,0 +1,20 @@
1
+ export declare function connectRemoteMCP(serverUrl: string, deviceToken: string): Promise<{
2
+ success: boolean;
3
+ message: string;
4
+ status?: any;
5
+ }>;
6
+ export declare function disconnectRemoteMCP(): {
7
+ success: boolean;
8
+ message: string;
9
+ };
10
+ export declare function getRemoteMCPStatus(): {
11
+ connected: boolean;
12
+ authenticated: boolean;
13
+ deviceId: string | null;
14
+ message: string;
15
+ };
16
+ export declare function executeRemoteMCP(method: string, params?: any): Promise<{
17
+ success: boolean;
18
+ result?: any;
19
+ error?: string;
20
+ }>;