Improve MCP startup flow and repo automation
This commit is contained in:
+95
-11
@@ -3,6 +3,7 @@
|
||||
const http = require('http');
|
||||
const IMAGE_DATA_URI_PREFIX = 'data:image/png;base64,';
|
||||
const LOG_PREFIX = '[Funplay Cocos MCP Server]';
|
||||
const MAX_PORT_FALLBACK_ATTEMPTS = 20;
|
||||
|
||||
function json(response, statusCode, payload) {
|
||||
response.writeHead(statusCode, { 'Content-Type': 'application/json; charset=utf-8' });
|
||||
@@ -42,20 +43,42 @@ class McpServer {
|
||||
this.serverName = options.serverName;
|
||||
this.serverVersion = options.serverVersion;
|
||||
this.server = null;
|
||||
this.actualPort = null;
|
||||
this.portFallbackInfo = null;
|
||||
}
|
||||
|
||||
isRunning() {
|
||||
return Boolean(this.server && this.server.listening);
|
||||
}
|
||||
|
||||
getPort() {
|
||||
if (this.server && typeof this.server.address === 'function') {
|
||||
const address = this.server.address();
|
||||
if (address && typeof address.port === 'number') {
|
||||
return address.port;
|
||||
}
|
||||
}
|
||||
return this.actualPort || this.config.port;
|
||||
}
|
||||
|
||||
getRequestedPort() {
|
||||
return this.config.port;
|
||||
}
|
||||
|
||||
getPortFallbackInfo() {
|
||||
return this.portFallbackInfo;
|
||||
}
|
||||
|
||||
async start() {
|
||||
if (this.isRunning()) {
|
||||
console.log(`${LOG_PREFIX} Start skipped: already running.`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`${LOG_PREFIX} Creating HTTP server on ${this.config.host}:${this.config.port}...`);
|
||||
this.server = http.createServer(async (request, response) => {
|
||||
this.actualPort = null;
|
||||
this.portFallbackInfo = null;
|
||||
|
||||
const requestHandler = async (request, response) => {
|
||||
try {
|
||||
if (request.method === 'GET' && request.url === '/health') {
|
||||
console.log(`${LOG_PREFIX} GET /health`);
|
||||
@@ -88,16 +111,59 @@ class McpServer {
|
||||
console.error(`${LOG_PREFIX} Request handling failed: ${error.message}`);
|
||||
return json(response, 500, this.createError(null, -32603, `Internal error: ${error.message}`));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
this.server.once('error', reject);
|
||||
this.server.listen(this.config.port, this.config.host, () => {
|
||||
this.server.off('error', reject);
|
||||
console.log(`${LOG_PREFIX} Listening on http://${this.config.host}:${this.config.port}/`);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
let attempt = 0;
|
||||
let port = this.config.port;
|
||||
let lastError = null;
|
||||
|
||||
while (attempt <= MAX_PORT_FALLBACK_ATTEMPTS) {
|
||||
console.log(`${LOG_PREFIX} Creating HTTP server on ${this.config.host}:${port}...`);
|
||||
const candidate = http.createServer(requestHandler);
|
||||
|
||||
try {
|
||||
await this.listen(candidate, port, this.config.host);
|
||||
this.server = candidate;
|
||||
this.actualPort = candidate.address() && typeof candidate.address().port === 'number'
|
||||
? candidate.address().port
|
||||
: port;
|
||||
|
||||
if (this.actualPort !== this.config.port) {
|
||||
this.portFallbackInfo = {
|
||||
requestedPort: this.config.port,
|
||||
actualPort: this.actualPort,
|
||||
attempts: attempt,
|
||||
};
|
||||
console.warn(
|
||||
`${LOG_PREFIX} Port ${this.config.port} was unavailable. ` +
|
||||
`Fell back to ${this.actualPort}.`
|
||||
);
|
||||
}
|
||||
|
||||
console.log(`${LOG_PREFIX} Listening on http://${this.config.host}:${this.actualPort}/`);
|
||||
return;
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
if (error && error.code === 'EADDRINUSE' && port < 65535 && attempt < MAX_PORT_FALLBACK_ATTEMPTS) {
|
||||
const nextPort = port + 1;
|
||||
console.warn(
|
||||
`${LOG_PREFIX} Port ${port} is already in use. ` +
|
||||
`Trying fallback port ${nextPort}...`
|
||||
);
|
||||
port = nextPort;
|
||||
attempt += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
candidate.removeAllListeners();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.server = null;
|
||||
this.actualPort = null;
|
||||
this.portFallbackInfo = null;
|
||||
throw lastError || new Error('Failed to start MCP server.');
|
||||
}
|
||||
|
||||
async stop() {
|
||||
@@ -109,6 +175,8 @@ class McpServer {
|
||||
console.log(`${LOG_PREFIX} Closing HTTP server...`);
|
||||
const active = this.server;
|
||||
this.server = null;
|
||||
this.actualPort = null;
|
||||
this.portFallbackInfo = null;
|
||||
await new Promise((resolve, reject) => {
|
||||
active.close((error) => {
|
||||
if (error) {
|
||||
@@ -122,6 +190,22 @@ class McpServer {
|
||||
});
|
||||
}
|
||||
|
||||
listen(server, port, host) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const onError = (error) => {
|
||||
server.off('listening', onListening);
|
||||
reject(error);
|
||||
};
|
||||
const onListening = () => {
|
||||
server.off('error', onError);
|
||||
resolve();
|
||||
};
|
||||
server.once('error', onError);
|
||||
server.once('listening', onListening);
|
||||
server.listen(port, host);
|
||||
});
|
||||
}
|
||||
|
||||
readBody(request) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const chunks = [];
|
||||
|
||||
Reference in New Issue
Block a user