mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-22 02:27:44 +08:00
feat(ql3): harden copilot mcp host deployment
This commit is contained in:
@@ -2,14 +2,46 @@
|
||||
|
||||
import { serveStdio } from '@modelcontextprotocol/server/stdio';
|
||||
|
||||
import { probeClusterCopilotClientReadiness } from '../copilot-client/client';
|
||||
import { readClusterCopilotMcpServerConfig } from './config';
|
||||
import { createQingLongClusterCopilotMcpServer } from './server';
|
||||
|
||||
const USAGE = 'Usage: ql3-copilot-mcp --config /absolute/private-config.json';
|
||||
const USAGE = [
|
||||
'Usage: ql3-copilot-mcp --config /absolute/private-config.json [--concurrency-ceiling=1..16]',
|
||||
' ql3-copilot-mcp --check --config /absolute/private-config.json [--concurrency-ceiling=1..16]',
|
||||
].join('\n');
|
||||
|
||||
function configArgument(argv: readonly string[]): string | null {
|
||||
if (argv.length !== 2 || argv[0] !== '--config' || !argv[1]) return null;
|
||||
return argv[1];
|
||||
interface ClusterCopilotMcpCliArguments {
|
||||
readonly check: boolean;
|
||||
readonly configFile: string;
|
||||
readonly concurrencyCeiling: number;
|
||||
}
|
||||
|
||||
function configArgument(
|
||||
argv: readonly string[],
|
||||
): Readonly<ClusterCopilotMcpCliArguments> | null {
|
||||
const check = argv[0] === '--check';
|
||||
const offset = check ? 1 : 0;
|
||||
if (
|
||||
(argv.length !== offset + 2 && argv.length !== offset + 3) ||
|
||||
argv[offset] !== '--config' ||
|
||||
!argv[offset + 1]
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
let concurrencyCeiling = 16;
|
||||
if (argv.length === offset + 3) {
|
||||
const match = /^--concurrency-ceiling=([1-9]|1[0-6])$/u.exec(
|
||||
argv[offset + 2] ?? '',
|
||||
);
|
||||
if (!match) return null;
|
||||
concurrencyCeiling = Number(match[1]);
|
||||
}
|
||||
return Object.freeze({
|
||||
check,
|
||||
configFile: argv[offset + 1]!,
|
||||
concurrencyCeiling,
|
||||
});
|
||||
}
|
||||
|
||||
function fact(event: 'process_failed' | 'transport_error'): string {
|
||||
@@ -26,8 +58,8 @@ async function main(argv: readonly string[]): Promise<void> {
|
||||
process.stdout.write(`${USAGE}\n`);
|
||||
return;
|
||||
}
|
||||
const configFile = configArgument(argv);
|
||||
if (configFile === null) {
|
||||
const command = configArgument(argv);
|
||||
if (command === null) {
|
||||
process.stderr.write(
|
||||
`${JSON.stringify({
|
||||
code: 'QL3_CLUSTER_COPILOT_MCP_CLI_USAGE_INVALID',
|
||||
@@ -46,7 +78,33 @@ async function main(argv: readonly string[]): Promise<void> {
|
||||
};
|
||||
|
||||
try {
|
||||
const config = readClusterCopilotMcpServerConfig(configFile);
|
||||
const config = readClusterCopilotMcpServerConfig(command.configFile);
|
||||
if (config.maxConcurrentRequests > command.concurrencyCeiling) {
|
||||
throw new TypeError('Cluster Copilot MCP concurrency ceiling exceeded');
|
||||
}
|
||||
if (command.check) {
|
||||
const readiness = await probeClusterCopilotClientReadiness(
|
||||
config.clientConfigFile,
|
||||
);
|
||||
process.stdout.write(
|
||||
`${JSON.stringify({
|
||||
schemaVersion: 1,
|
||||
component: 'qinglong3-cluster-copilot-mcp',
|
||||
event: 'preflight_checked',
|
||||
transport: readiness.transport,
|
||||
ready: readiness.ready,
|
||||
configuration: 'valid',
|
||||
credential: 'valid',
|
||||
maxConcurrentRequests: config.maxConcurrentRequests,
|
||||
concurrencyCeiling: command.concurrencyCeiling,
|
||||
requestMethod: 'GET',
|
||||
requestPath: '/readyz',
|
||||
mutation: false,
|
||||
})}\n`,
|
||||
);
|
||||
if (!readiness.ready) process.exitCode = 69;
|
||||
return;
|
||||
}
|
||||
handle = serveStdio(
|
||||
() => createQingLongClusterCopilotMcpServer({ config }),
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user