Skip to content

Commit 7663897

Browse files
committed
feat(ai): Harden Neural Link MCP Server (#8016)
- Implement HealthService for WebSocket and session monitoring - Add startup health checks and logging in Server.mjs - Update ConnectionService to expose server status - Enhance OpenAPI specs with health endpoint and detailed descriptions
1 parent 91bf58a commit 7663897

5 files changed

Lines changed: 247 additions & 15 deletions

File tree

ai/mcp/server/neural-link/Server.mjs

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import Base from '../../../../src/core/Base.mjs';
55
import aiConfig from './config.mjs';
66
import logger from './logger.mjs';
77
import ConnectionService from './services/ConnectionService.mjs';
8+
import HealthService from './services/HealthService.mjs';
89
import { listTools, callTool } from './services/toolService.mjs';
910

1011
/**
@@ -56,18 +57,35 @@ class Server extends Base {
5657
// 3. Setup Handlers
5758
this.setupRequestHandlers();
5859

59-
// 4. Start Connection Service (WebSocket)
60-
if (!ConnectionService.isReady) {
61-
// Ensure initialized
62-
}
60+
// 4. Wait for Connection Service
61+
await ConnectionService.ready();
62+
63+
// 5. Perform Health Check & Log Status
64+
const health = await HealthService.healthcheck();
65+
this.logStartupStatus(health);
6366

64-
// 5. Connect Transport (Stdio)
67+
// 6. Connect Transport (Stdio)
6568
this.transport = new StdioServerTransport();
6669
await this.mcpServer.connect(this.transport);
6770

6871
logger.info('Neural Link MCP Server started');
6972
}
7073

74+
/**
75+
* Logs the health status of the server during startup.
76+
* @param {Object} health The health check result object.
77+
*/
78+
logStartupStatus(health) {
79+
if (health.status === 'unhealthy') {
80+
logger.warn('⚠️ [Startup] Neural Link is unhealthy. Server will start but tools will fail until resolved.');
81+
health.details.forEach(detail => logger.warn(` ${detail}`));
82+
} else {
83+
logger.info('✅ [Startup] Neural Link health check passed');
84+
logger.info(` - Active Sessions: ${health.server.activeSessions}`);
85+
logger.info(` - Connected Windows: ${health.server.connectedWindows}`);
86+
}
87+
}
88+
7189
setupRequestHandlers() {
7290
// List Tools Handler
7391
this.mcpServer.server.setRequestHandler(ListToolsRequestSchema, async (request) => {
@@ -91,8 +109,24 @@ class Server extends Base {
91109
// Call Tool Handler
92110
this.mcpServer.server.setRequestHandler(CallToolRequestSchema, async (request) => {
93111
const { name, arguments: args } = request.params;
112+
94113
try {
95114
logger.debug(`[MCP] Calling tool: ${name} with params:`, JSON.stringify(request.params));
115+
116+
// Health Check Gate
117+
if (name !== 'healthcheck') {
118+
const health = await HealthService.healthcheck();
119+
if (health.status !== 'healthy') {
120+
return {
121+
content: [{
122+
type: 'text',
123+
text: `Cannot execute ${name}: Neural Link is unhealthy.\nDetails: ${health.details.join(', ')}`
124+
}],
125+
isError: true
126+
};
127+
}
128+
}
129+
96130
const result = await callTool(name, args);
97131

98132
return {

ai/mcp/server/neural-link/openapi.yaml

Lines changed: 114 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,49 @@ info:
33
title: Neo.mjs Neural Link MCP Server
44
version: 1.0.0
55
paths:
6+
/health:
7+
post:
8+
summary: Health Check
9+
operationId: healthcheck
10+
x-annotations:
11+
readOnlyHint: true
12+
description: |
13+
Checks the health of the Neural Link server, including WebSocket connectivity and active sessions.
14+
15+
**When to Use:**
16+
Use this as a first-step diagnostic tool to ensure the Neural Link bridge to the browser is operational.
17+
tags: [Health]
18+
requestBody:
19+
content:
20+
application/json:
21+
schema:
22+
type: object
23+
properties: {}
24+
responses:
25+
'200':
26+
description: Server is healthy
27+
content:
28+
application/json:
29+
schema:
30+
$ref: '#/components/schemas/HealthCheckResponse'
31+
'503':
32+
description: Server is unhealthy
33+
content:
34+
application/json:
35+
schema:
36+
$ref: '#/components/schemas/ErrorResponse'
37+
638
/component/property/get:
739
post:
840
summary: Get Component Property
941
operationId: get_component_property
1042
x-pass-as-object: true
11-
description: Retrieves a property from a component by its ID.
43+
description: |
44+
Retrieves a property from a component by its ID.
45+
46+
**When to Use:**
47+
To inspect the current state of a component (e.g., `width`, `store`, `value`).
48+
tags: [Component]
1249
requestBody:
1350
required: true
1451
content:
@@ -40,7 +77,12 @@ paths:
4077
summary: Get Component Tree
4178
operationId: get_component_tree
4279
x-pass-as-object: true
43-
description: Retrieves the full component tree of the application.
80+
description: |
81+
Retrieves the full component tree of the application.
82+
83+
**When to Use:**
84+
To understand the structure of the UI, find component IDs, or debug hierarchy issues.
85+
tags: [Component]
4486
requestBody:
4587
content:
4688
application/json:
@@ -72,7 +114,12 @@ paths:
72114
summary: Get Component VDOM Tree
73115
operationId: get_vdom_tree
74116
x-pass-as-object: true
75-
description: Retrieves the VDOM tree of a component.
117+
description: |
118+
Retrieves the VDOM tree of a component.
119+
120+
**When to Use:**
121+
To inspect the Virtual DOM structure of a component for rendering issues.
122+
tags: [Component]
76123
requestBody:
77124
content:
78125
application/json:
@@ -103,7 +150,12 @@ paths:
103150
summary: Get Component VNode Tree
104151
operationId: get_vnode_tree
105152
x-pass-as-object: true
106-
description: Retrieves the VNode tree of a component.
153+
description: |
154+
Retrieves the VNode tree of a component.
155+
156+
**When to Use:**
157+
To inspect the actual DOM-aligned VNodes (after VDOM processing). Useful for deep rendering debugging.
158+
tags: [Component]
107159
requestBody:
108160
content:
109161
application/json:
@@ -134,7 +186,12 @@ paths:
134186
summary: Get Drag State
135187
operationId: get_drag_state
136188
x-pass-as-object: true
137-
description: Retrieves the state of the DragCoordinator.
189+
description: |
190+
Retrieves the state of the DragCoordinator.
191+
192+
**When to Use:**
193+
To debug drag-and-drop operations, checking what is being dragged and where.
194+
tags: [Inspection]
138195
requestBody:
139196
content:
140197
application/json:
@@ -157,7 +214,12 @@ paths:
157214
summary: Reload Page
158215
operationId: reload_page
159216
x-pass-as-object: true
160-
description: Reloads the application page.
217+
description: |
218+
Reloads the application page.
219+
220+
**When to Use:**
221+
To force a full reload of the connected application window.
222+
tags: [Lifecycle]
161223
requestBody:
162224
content:
163225
application/json:
@@ -183,7 +245,12 @@ paths:
183245
post:
184246
summary: Get Worker Topology
185247
operationId: get_worker_topology
186-
description: Retrieves the topology of all connected App Workers.
248+
description: |
249+
Retrieves the topology of all connected App Workers.
250+
251+
**When to Use:**
252+
To discover connected workers, their session IDs, and environments.
253+
tags: [Topology]
187254
requestBody:
188255
content:
189256
application/json:
@@ -217,7 +284,12 @@ paths:
217284
post:
218285
summary: Get Window Topology
219286
operationId: get_window_topology
220-
description: Retrieves the topology of all connected windows.
287+
description: |
288+
Retrieves the topology of all connected windows.
289+
290+
**When to Use:**
291+
To map logical window IDs to physical browser windows and their dimensions.
292+
tags: [Topology]
221293
requestBody:
222294
content:
223295
application/json:
@@ -250,7 +322,12 @@ paths:
250322
summary: Set Component Property
251323
operationId: set_component_property
252324
x-pass-as-object: true
253-
description: Sets a property on a component by its ID.
325+
description: |
326+
Sets a property on a component by its ID.
327+
328+
**When to Use:**
329+
To modify the runtime state of a component (e.g., change text, toggle visibility).
330+
tags: [Component]
254331
requestBody:
255332
required: true
256333
content:
@@ -275,6 +352,33 @@ paths:
275352

276353
components:
277354
schemas:
355+
HealthCheckResponse:
356+
type: object
357+
properties:
358+
status:
359+
type: string
360+
enum: [healthy, unhealthy, degraded]
361+
timestamp:
362+
type: string
363+
format: date-time
364+
server:
365+
type: object
366+
properties:
367+
port:
368+
type: integer
369+
activeSessions:
370+
type: integer
371+
connectedWindows:
372+
type: integer
373+
details:
374+
type: array
375+
items:
376+
type: string
377+
version:
378+
type: string
379+
uptime:
380+
type: number
381+
278382
GetComponentPropertyRequest:
279383
type: object
280384
required:
@@ -335,4 +439,4 @@ components:
335439
properties:
336440
error:
337441
type: string
338-
description: Error message
442+
description: Error message

ai/mcp/server/neural-link/services/ConnectionService.mjs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,27 @@ class ConnectionService extends Base {
349349
return Array.from(this.sessionData.values())
350350
}
351351

352+
/**
353+
* Returns the current status of the server.
354+
* @returns {Object}
355+
*/
356+
getStatus() {
357+
const windows = [];
358+
359+
for (const meta of this.sessionData.values()) {
360+
if (meta.windows) {
361+
for (const win of meta.windows.values()) {
362+
windows.push(win)
363+
}
364+
}
365+
}
366+
367+
return {
368+
sessions: this.sessions.size,
369+
windows
370+
}
371+
}
372+
352373
/**
353374
* Reloads the application page.
354375
* @param {Object} opts The options object.
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import Base from '../../../../../src/core/Base.mjs';
2+
import ConnectionService from './ConnectionService.mjs';
3+
import logger from '../logger.mjs';
4+
5+
/**
6+
* @summary Monitors the health of the Neural Link MCP Server.
7+
*
8+
* This service checks the status of the WebSocket server and the active connections
9+
* to the App Worker(s). It provides a `healthcheck` tool that agents can use
10+
* to verify if the runtime bridge is operational.
11+
*
12+
* @class Neo.ai.mcp.server.neural-link.services.HealthService
13+
* @extends Neo.core.Base
14+
* @singleton
15+
*/
16+
class HealthService extends Base {
17+
static config = {
18+
/**
19+
* @member {String} className='Neo.ai.mcp.server.neural-link.services.HealthService'
20+
* @protected
21+
*/
22+
className: 'Neo.ai.mcp.server.neural-link.services.HealthService',
23+
/**
24+
* @member {Boolean} singleton=true
25+
* @protected
26+
*/
27+
singleton: true
28+
}
29+
30+
/**
31+
* Checks the health of the Neural Link server.
32+
* @returns {Promise<Object>} The health status payload.
33+
*/
34+
async healthcheck() {
35+
try {
36+
const status = ConnectionService.getStatus();
37+
const details = [];
38+
39+
if (status.sessions === 0) {
40+
details.push('No active App Worker sessions');
41+
} else {
42+
details.push(`${status.sessions} active App Worker session(s)`);
43+
details.push(`${status.windows.length} connected window(s)`);
44+
}
45+
46+
return {
47+
status : 'healthy',
48+
timestamp: new Date().toISOString(),
49+
server : {
50+
port : ConnectionService.port,
51+
activeSessions : status.sessions,
52+
connectedWindows: status.windows.length
53+
},
54+
details,
55+
version : process.env.npm_package_version || '1.0.0',
56+
uptime : process.uptime()
57+
};
58+
} catch (error) {
59+
logger.error('[HealthService] Unexpected error during health check:', error);
60+
return {
61+
status : 'unhealthy',
62+
details: [`Unexpected error: ${error.message}`],
63+
error : 'Health check failed unexpectedly',
64+
message: error.message,
65+
code : 'HEALTH_CHECK_ERROR'
66+
};
67+
}
68+
}
69+
}
70+
71+
export default Neo.setupClass(HealthService);

ai/mcp/server/neural-link/services/toolService.mjs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import path from 'path';
22
import {fileURLToPath} from 'url';
33
import ToolService from '../../../ToolService.mjs';
44
import ConnectionService from './ConnectionService.mjs';
5+
import HealthService from './HealthService.mjs';
56

67
const __filename = fileURLToPath(import.meta.url);
78
const __dirname = path.dirname(__filename);
@@ -15,6 +16,7 @@ const serviceMapping = {
1516
get_vnode_tree : ConnectionService.getVnodeTree.bind(ConnectionService),
1617
get_window_topology : ConnectionService.getWindowTopology.bind(ConnectionService),
1718
get_worker_topology : ConnectionService.getWorkerTopology.bind(ConnectionService),
19+
healthcheck : HealthService.healthcheck.bind(HealthService),
1820
reload_page : ConnectionService.reloadPage.bind(ConnectionService),
1921
set_component_property: ConnectionService.setComponentProperty.bind(ConnectionService)
2022
};

0 commit comments

Comments
 (0)