Files
MikrotikManager/backend/scripts/mcp-external-process/run-owned.mjs
T

37 lines
3.9 KiB
JavaScript

import EmbeddedPostgres from 'embedded-postgres'
import { mkdtemp, rm, mkdir, readFile, writeFile } from 'node:fs/promises'
import { join, resolve, relative, isAbsolute } from 'node:path'
import { fileURLToPath } from 'node:url'
import { randomBytes, createHash } from 'node:crypto'
import { createServer } from 'node:net'
import { spawn } from 'node:child_process'
const sourceRoot = resolve(process.argv[2] ?? fileURLToPath(new URL('../../../', import.meta.url)))
const output = resolve(sourceRoot, 'tmp/mcp-external-process-acceptance'); await mkdir(output, { recursive: true })
const paths = ['backend/src/scripts/mcp-stdio.ts', 'backend/src/routes/mcp-transport.ts', 'backend/src/routes/mcp-oauth.ts', 'backend/src/modules/mcp/store.ts',
'backend/src/modules/mcp/runtime.ts', 'backend/src/modules/mcp/actions.ts', 'backend/src/modules/mcp/security.ts', 'backend/src/modules/mcp/protocol-limits.ts',
'backend/src/modules/mcp/portal-revocation.ts', 'backend/src/plugins/auth.ts', 'backend/src/index.ts', 'node_modules/@modelcontextprotocol/client/package.json', 'node_modules/@modelcontextprotocol/server/package.json']
async function pins() { const result = []; for (const path of paths) result.push({ path, sha256: createHash('sha256').update(await readFile(resolve(sourceRoot, path))).digest('hex') }); return result }
const before = await pins(), directory = await mkdtemp(join(resolve(sourceRoot, 'tmp'), 'mm-mcp-external-')), state = join(directory, 'state'); await mkdir(state)
const listener = createServer(); await new Promise(resolve => listener.listen(0, '127.0.0.1', resolve)); const port = listener.address().port; await new Promise(resolve => listener.close(resolve))
const password = randomBytes(24).toString('hex')
const postgres = new EmbeddedPostgres({ databaseDir: join(directory, 'pg'), port, user: 'mcp_test', password, persistent: true, createPostgresUser: false,
initdbFlags: ['--encoding=UTF8'], postgresFlags: ['-h', '127.0.0.1'], onLog: () => {}, onError: () => {} })
try {
await postgres.initialise(); await postgres.start()
process.exitCode = await new Promise((done, reject) => {
const child = spawn(process.execPath, ['--import', import.meta.resolve('tsx'), fileURLToPath(new URL('./server-fixture.ts', import.meta.url))], { cwd: directory,
windowsHide: true, stdio: 'inherit', env: { ...Object.fromEntries(['SystemRoot', 'WINDIR', 'TEMP', 'TMP', 'PATH', 'Path'].filter(k => process.env[k]).map(k => [k, process.env[k]])),
OWN_EXTERNAL_CLEANUP_FAULT: process.env.OWN_EXTERNAL_CLEANUP_FAULT, OWN_EXTERNAL_SOURCE_ROOT: sourceRoot, OWN_EXTERNAL_RESULT_FILE: join(output, 'result.json'), NODE_ENV: 'test', OWN_EXTERNAL_PROCESS_PG: 'true', DATABASE_URL: `postgresql://mcp_test:${password}@127.0.0.1:${port}/postgres`, DATABASE_PATH: join(directory, 'no-legacy.sqlite'),
MCP_STATE_DIR: state, AUTH_REQUIRED: 'true', AUTH_JWT_SECRET: 'owned-external-synthetic-issuer-test-only', AUTH_ISSUER: 'https://synthetic-portal.invalid', AUTH_PORTAL_URL: 'https://synthetic-portal.invalid' } })
child.once('error', reject); child.once('exit', code => done(code ?? 1))
})
const after = await pins(), unchanged = JSON.stringify(before) === JSON.stringify(after)
await writeFile(join(output, 'source-pins.json'), JSON.stringify({ before, after, unchanged, exitCode: process.exitCode }, null, 2))
console.log('External-client source/SDK pins unchanged:', unchanged); if (!unchanged) process.exitCode = 1
} finally {
await postgres.stop(); const target = resolve(directory), rel = relative(resolve(resolve(sourceRoot, 'tmp')), target)
if (!rel || rel.startsWith('..') || isAbsolute(rel) || !target.includes('mm-mcp-external-')) throw Error('Unsafe exact owned cleanup')
await rm(target, { recursive: true, force: true, maxRetries: 10, retryDelay: 250 }); console.log('Owned external-client PostgreSQL stopped and exact temp directory removed')
}
process.exit(process.exitCode ?? 0)