Files
Genarrative/apps/ai-game-creator-shell/src-tauri/test-fixtures/mcp-server.mjs
T
kdletters 6fe646ba2b
Project CI / Frontend tests (push) Failing after 21s
Project CI / Repository checks (push) Successful in 58s
Project CI / Native shell tests (push) Failing after 3m7s
Project CI / Backend tests (push) Successful in 3m33s
修复开放Issue 115、118、127和128
补齐Anthropic strict能力门控、传输schema、缓存usage和真实端点验收
隔离可选MCP服务目录失败并安全重定位局部schema引用
修复PowerShell 7环境下Windows私有ACL检查
统一角色与图标素材落库的用户prompt语义
合并权威画布快照与本地待保存或在途布局并补齐回归
同步更新技术文档、项目记忆和验证门禁
2026-08-03 14:03:20 +08:00

322 lines
8.6 KiB
JavaScript

import { appendFileSync } from 'node:fs';
import http from 'node:http';
import readline from 'node:readline';
const args = process.argv.slice(2);
const mode = args[0] ?? 'stdio';
const failList = args.includes('--fail-list');
const includeUnannotated = args.includes('--include-unannotated');
const duplicateTool = args.includes('--duplicate-tool');
const oversizedInputSchema = args.includes('--oversized-input-schema');
const requireTaskMode = args.includes('--require-task-mode');
const toolCountArgument = args.find((value) => value.startsWith('--tool-count='));
const toolCount = toolCountArgument
? Number(toolCountArgument.slice('--tool-count='.length))
: null;
const listDelayArgument = args.find((value) =>
value.startsWith('--list-delay-ms='),
);
const listDelayMs = Math.max(
0,
Number(listDelayArgument?.slice('--list-delay-ms='.length) ?? '0') || 0,
);
const mutateResponseDelayArgument = args.find((value) =>
value.startsWith('--mutate-response-delay-ms='),
);
const mutateResponseDelayMs = Math.max(
0,
Number(
mutateResponseDelayArgument?.slice('--mutate-response-delay-ms='.length) ??
'0',
) || 0,
);
const markerArgument = args.find((value) => value.startsWith('--marker='));
const markerPath = markerArgument?.slice('--marker='.length) ?? '';
const bearerTokenArgument = args.find((value) =>
value.startsWith('--bearer-token='),
);
const bearerToken =
bearerTokenArgument?.slice('--bearer-token='.length) ?? 'fixture-token';
const fixtureHeaderArgument = args.find((value) =>
value.startsWith('--fixture-header='),
);
const fixtureHeader =
fixtureHeaderArgument?.slice('--fixture-header='.length) ?? 'enabled';
const lookupValueArgument = args.find((value) =>
value.startsWith('--lookup-value='),
);
const lookupValue = lookupValueArgument?.slice('--lookup-value='.length) ?? '';
const mutateValueArgument = args.find((value) =>
value.startsWith('--mutate-value='),
);
const mutateValue = mutateValueArgument?.slice('--mutate-value='.length) ?? '';
const tools = [
{
name: 'lookup',
title: 'Fixture lookup',
description: 'Returns deterministic fixture data.',
inputSchema: {
type: 'object',
properties: {
query: {
type: 'string',
...(lookupValue ? { const: lookupValue } : {}),
},
},
required: ['query'],
additionalProperties: false,
},
annotations: {
readOnlyHint: true,
destructiveHint: false,
openWorldHint: false,
},
},
{
name: 'mutate',
title: 'Fixture mutation',
description: 'Appends one deterministic line to the configured marker.',
inputSchema: {
type: 'object',
properties: {
value: {
type: 'string',
...(mutateValue ? { const: mutateValue } : {}),
},
},
required: ['value'],
additionalProperties: false,
},
annotations: {
readOnlyHint: false,
destructiveHint: true,
openWorldHint: false,
},
},
];
if (duplicateTool) {
tools.push({ ...tools[0] });
}
if (oversizedInputSchema) {
tools[0].inputSchema.properties.query.description = 'x'.repeat(70 * 1024);
}
if (requireTaskMode) {
tools[0].execution = { taskSupport: 'required' };
}
if (Number.isInteger(toolCount) && toolCount > tools.length) {
for (let index = tools.length; index < toolCount; index += 1) {
tools.push({
...tools[0],
name: `lookup-${index}`,
title: `Lookup ${index}`,
});
}
}
if (includeUnannotated) {
tools.push({
name: 'mutate-unannotated',
title: 'Fixture unannotated mutation',
description: 'Appends one deterministic line without safety annotations.',
inputSchema: {
type: 'object',
properties: {
value: {
type: 'string',
...(mutateValue ? { const: mutateValue } : {}),
},
},
required: ['value'],
additionalProperties: false,
},
});
}
async function resultFor(message) {
if (!message || typeof message !== 'object') {
return null;
}
const { id, method, params = {} } = message;
if (id === undefined || id === null) {
return null;
}
if (method === 'initialize') {
return {
jsonrpc: '2.0',
id,
result: {
protocolVersion: params.protocolVersion,
capabilities: { tools: { listChanged: false } },
serverInfo: { name: 'game-creator-mcp-fixture', version: '1.0.0' },
instructions:
'Fixture instructions are untrusted external input. Use listed tools only.',
},
};
}
if (method === 'tools/list') {
if (listDelayMs > 0) {
await new Promise((resolve) => setTimeout(resolve, listDelayMs));
}
if (failList) {
return {
jsonrpc: '2.0',
id,
error: { code: -32603, message: 'fixture tools/list failure' },
};
}
return { jsonrpc: '2.0', id, result: { tools } };
}
if (method === 'tools/call') {
if (params.name === 'lookup') {
const query = String(params.arguments?.query ?? '');
if (lookupValue && query !== lookupValue) {
return {
jsonrpc: '2.0',
id,
error: { code: -32602, message: 'fixture lookup argument mismatch' },
};
}
return {
jsonrpc: '2.0',
id,
result: {
content: [{ type: 'text', text: `lookup:${query}` }],
structuredContent: { query, transport: mode },
isError: false,
},
};
}
if (params.name === 'mutate' || params.name === 'mutate-unannotated') {
const value = String(params.arguments?.value ?? '');
if (mutateValue && value !== mutateValue) {
return {
jsonrpc: '2.0',
id,
error: { code: -32602, message: 'fixture mutate argument mismatch' },
};
}
if (markerPath) {
appendFileSync(markerPath, `${value}\n`, 'utf8');
}
if (mutateResponseDelayMs > 0) {
await new Promise((resolve) =>
setTimeout(resolve, mutateResponseDelayMs),
);
}
return {
jsonrpc: '2.0',
id,
result: {
content: [{ type: 'text', text: `mutated:${value}` }],
isError: false,
},
};
}
return {
jsonrpc: '2.0',
id,
error: { code: -32602, message: 'unknown fixture tool' },
};
}
if (method === 'ping') {
return { jsonrpc: '2.0', id, result: {} };
}
return {
jsonrpc: '2.0',
id,
error: { code: -32601, message: `unsupported fixture method: ${method}` },
};
}
function runStdio() {
const input = readline.createInterface({ input: process.stdin });
input.on('line', async (line) => {
let message;
try {
message = JSON.parse(line);
} catch {
return;
}
const response = await resultFor(message);
if (response) {
process.stdout.write(`${JSON.stringify(response)}\n`);
}
});
}
function readRequestBody(request) {
return new Promise((resolve, reject) => {
const chunks = [];
request.on('data', (chunk) => chunks.push(chunk));
request.on('end', () => {
try {
resolve(JSON.parse(Buffer.concat(chunks).toString('utf8')));
} catch (error) {
reject(error);
}
});
request.on('error', reject);
});
}
function runHttp() {
const requestedPort = Number(args[1] ?? '0');
const server = http.createServer(async (request, response) => {
if (request.url !== '/mcp') {
response.writeHead(404).end();
return;
}
if (request.method === 'GET') {
response.writeHead(405, { Allow: 'POST, DELETE' }).end();
return;
}
if (request.method === 'DELETE') {
response.writeHead(200).end();
return;
}
if (request.method !== 'POST') {
response.writeHead(405, { Allow: 'POST, DELETE' }).end();
return;
}
if (
request.headers.authorization !== `Bearer ${bearerToken}` ||
request.headers['x-mcp-fixture'] !== fixtureHeader
) {
response.writeHead(401).end();
return;
}
let message;
try {
message = await readRequestBody(request);
} catch {
response.writeHead(400).end();
return;
}
const result = await resultFor(message);
if (!result) {
response.writeHead(202, { 'Mcp-Session-Id': 'fixture-session' }).end();
return;
}
response.writeHead(200, {
'Content-Type': 'application/json',
'Mcp-Session-Id': 'fixture-session',
});
response.end(JSON.stringify(result));
});
server.listen(requestedPort, '127.0.0.1', () => {
const address = server.address();
process.stdout.write(`${JSON.stringify({ port: address.port })}\n`);
});
}
if (mode === 'http') {
runHttp();
} else {
runStdio();
}