import { describe, it } from 'node:test'; import assert from 'node:assert/strict'; import { resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; import { resetCounter } from '../../scanners/lib/output.mjs'; import { scan } from '../../scanners/token-hotspots.mjs'; import { discoverConfigFiles } from '../../scanners/lib/file-discovery.mjs'; import { withHermeticHome } from '../helpers/hermetic-home.mjs'; const __dirname = fileURLToPath(new URL('.', import.meta.url)); const FIXTURES = resolve(__dirname, '../fixtures'); // Hermetic HOME so readActiveConfig does not leak the developer's real // ~/.claude.json plugin MCP servers into the fixture result (mirrors the other // TOK tests). The deferral check reads project+local settings + project .mcp.json. async function runScanner(fixtureName) { resetCounter(); const path = resolve(FIXTURES, fixtureName); const discovery = await discoverConfigFiles(path); return withHermeticHome(() => scan(path, discovery)); } const TITLE = 'MCP tool schemas forced into the always-loaded prefix'; const findDeferral = (result) => result.findings.find(f => f.title === TITLE); describe('TOK scanner — MCP deferral (v5.10 B4, CA-TOK-006)', () => { it('deferred (default) project → NO deferral finding', async () => { const result = await runScanner('mcp-deferral/deferred'); assert.equal(findDeferral(result), undefined, 'a plain server with tool search on must not be flagged as forced-upfront'); }); it('alwaysLoad:true server → deferral finding fires (medium severity)', async () => { const result = await runScanner('mcp-deferral/alwaysload'); const f = findDeferral(result); assert.ok(f, `expected a deferral finding; got: ${result.findings.map(x => x.title).join(' | ')}`); assert.equal(f.severity, 'medium', `expected medium for ~2500 forced tokens, got ${f.severity}`); assert.equal(f.category, 'token-efficiency'); // Only the alwaysLoad server is named, not the deferred sibling. assert.match(String(f.evidence || ''), /always-srv/); assert.doesNotMatch(String(f.description || ''), /deferred-srv/); assert.match(String(f.evidence || ''), /alwaysLoad/); }); it('ENABLE_TOOL_SEARCH="false" in settings → deferral finding (all servers affected)', async () => { const result = await runScanner('mcp-deferral/disabled-settings'); const f = findDeferral(result); assert.ok(f, `expected a deferral finding for tool-search-disabled fixture`); assert.match(String(f.evidence || ''), /reason=enable-tool-search-false/); assert.match(String(f.description || ''), /Tool search is disabled/); }); it('every deferral finding discloses runtime conditions + calibration', async () => { const result = await runScanner('mcp-deferral/alwaysload'); const f = findDeferral(result); assert.ok(f); // DEFERRAL_DISCLOSURE names the launch/runtime conditions a static scan can't see. assert.match(String(f.evidence || ''), /Vertex AI/); assert.match(String(f.evidence || ''), /ANTHROPIC_BASE_URL/); // CALIBRATION_NOTE shared by all TOK pattern findings. assert.match(String(f.evidence || ''), /severity reflects estimated tokens\/turn/i); }); it('finding ID matches CA-TOK-NNN format', async () => { const result = await runScanner('mcp-deferral/alwaysload'); const f = findDeferral(result); assert.ok(f); assert.match(f.id, /^CA-TOK-\d{3}$/); }); });