chore: Enhance MCP server execution and compatibility with Cursor mode

- Introduce environment variables for Cursor compatibility in silent-mcp.sh and npx-entry.cjs
- Implement process cleanup for existing MCP instances to prevent conflicts
- Adjust logging behavior based on execution context to ensure proper message handling
- Add test-cursor.sh script to simulate Cursor environment for testing purposes
- Refactor stdio-server.ts to manage logging and message flushing based on compatibility mode
This commit is contained in:
jango-blockchained
2025-03-17 18:30:33 +01:00
parent 1bc11de465
commit 2d5ae034c9
4 changed files with 187 additions and 55 deletions

View File

@@ -5,8 +5,10 @@
* over standard input/output using JSON-RPC 2.0 protocol.
*/
// Force silent logging
process.env.LOG_LEVEL = 'silent';
// Only force silent logging if not in Cursor compatibility mode
if (!process.env.CURSOR_COMPATIBLE) {
process.env.LOG_LEVEL = 'silent';
}
import { createStdioServer, BaseTool } from "./mcp/index.js";
import { z } from "zod";
@@ -17,18 +19,36 @@ import { MCPContext } from "./mcp/types.js";
import { LightsControlTool } from './tools/homeassistant/lights.tool.js';
import { ClimateControlTool } from './tools/homeassistant/climate.tool.js';
// Check for silent startup mode - never silent in npx mode to ensure the JSON-RPC messages are sent
const silentStartup = true;
// Check for Cursor compatibility mode
const isCursorMode = process.env.CURSOR_COMPATIBLE === 'true';
// Use silent startup except in Cursor mode
const silentStartup = !isCursorMode;
const debugMode = process.env.DEBUG_STDIO === 'true';
// Send a notification directly to stdout for Cursor compatibility
// Configure raw I/O handling if necessary
if (isCursorMode) {
// Ensure stdout doesn't buffer for Cursor
process.stdout.setDefaultEncoding('utf8');
// Only try to set raw mode if it's a TTY and the method exists
if (process.stdout.isTTY && typeof (process.stdout as any).setRawMode === 'function') {
(process.stdout as any).setRawMode(true);
}
}
// Send a notification directly to stdout for compatibility
function sendNotification(method: string, params: any): void {
const notification = {
jsonrpc: '2.0',
method,
params
};
process.stdout.write(JSON.stringify(notification) + '\n');
const message = JSON.stringify(notification) + '\n';
process.stdout.write(message);
// For Cursor mode, ensure messages are flushed if method exists
if (isCursorMode && typeof (process.stdout as any).flush === 'function') {
(process.stdout as any).flush();
}
}
// Create system tools
@@ -79,19 +99,7 @@ async function main() {
// Combine all tools
const allTools = [...systemTools, ...haTools];
// Create server with stdio transport
const { server, transport } = createStdioServer({
silent: silentStartup,
debug: debugMode,
tools: allTools
});
// Explicitly set the server reference to ensure access to tools
if ('setServer' in transport && typeof transport.setServer === 'function') {
transport.setServer(server);
}
// Send initial notifications directly to stdout for Cursor compatibility
// Send initial notifications BEFORE server initialization for Cursor compatibility
// Send system info
sendNotification('system.info', {
name: 'Home Assistant Model Context Protocol Server',
@@ -118,9 +126,42 @@ async function main() {
tools: toolDefinitions
});
// Start the server
// Create server with stdio transport
const { server, transport } = createStdioServer({
silent: silentStartup,
debug: debugMode,
tools: allTools
});
// Explicitly set the server reference to ensure access to tools
if ('setServer' in transport && typeof transport.setServer === 'function') {
transport.setServer(server);
}
// Start the server after initial notifications
await server.start();
// In Cursor mode, send notifications again after startup
if (isCursorMode) {
// Small delay to ensure all messages are processed
setTimeout(() => {
// Send system info again
sendNotification('system.info', {
name: 'Home Assistant Model Context Protocol Server',
version: '1.0.0',
transport: 'stdio',
protocol: 'json-rpc-2.0',
features: ['streaming'],
timestamp: new Date().toISOString()
});
// Send available tools again
sendNotification('tools.available', {
tools: toolDefinitions
});
}, 100);
}
// Handle process exit
process.on('SIGINT', async () => {
await server.shutdown();