From 071c50b1406e6237f1fac4dc8918f7d42b61771f Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Wed, 30 Sep 2026 16:47:25 -0500 Subject: [PATCH 1/2] fix(scripts) Read Java MCP toolset registrations why: Current Java sources split registrations out of Catalog.java and qualify factory calls. Docs publication rejects all runtime tool names when extraction reads only the older catalog layout. what: - Read Catalog and the toolset classes with qualified factory calls - Keep source registration and runtime schema agreement mandatory - Verify both layouts and the registered source file and line Validation: all 45 tools from the native Java server match source sites. The old extractor fails that run; the replacement passes. The outer docs loop passes in 48.5 seconds, including type and browser checks. --- scripts/gen-mcp-tools.mjs | 10 +++---- site/test/mcp-source-bound.outer.test.ts | 33 +++++++++++++++++++++--- 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/scripts/gen-mcp-tools.mjs b/scripts/gen-mcp-tools.mjs index 0513c6f2..394ce99c 100644 --- a/scripts/gen-mcp-tools.mjs +++ b/scripts/gen-mcp-tools.mjs @@ -87,11 +87,11 @@ const PORTS = [ { slug: 'java', dir: '~/work/libtmux/libtmux-java/libtmux-mcp/src/main/java/io/github/libtmux/mcp', - glob: 'Catalog.java', - // Anchored on `tools.add`, the registration itself, because the catalog - // reaches it through five factories and two decorators. Yields exactly the - // 45 names of `CapabilityRegistryTest.CATALOG_ORDER`. - pattern: /tools\.add\(\s*(?:\w+\(\s*)+"([a-z][a-z0-9_]*)"/gs, + glob: '{Catalog,*Tools}.java', + // Older revisions register in Catalog; newer ones split by toolset and + // qualify factories and decorators with Catalog. Anchor on registration + // so schema arguments and helper calls cannot become tool names. + pattern: /tools\.add\(\s*(?:(?:\w+\.)*\w+\(\s*)+"([a-z][a-z0-9_]*)"/gs, }, { slug: 'dotnet', diff --git a/site/test/mcp-source-bound.outer.test.ts b/site/test/mcp-source-bound.outer.test.ts index 547f23fa..1665d0e5 100644 --- a/site/test/mcp-source-bound.outer.test.ts +++ b/site/test/mcp-source-bound.outer.test.ts @@ -15,11 +15,11 @@ const tool = { name: 'list_sessions', description: 'Selected source contract', i type: 'object', properties: { label: { type: 'string', description: 'Selected source argument' } }, } } -function fixture(slug: 'go' | 'py' | 'ruby', run: (fixture: { +function fixture(slug: 'go' | 'py' | 'ruby' | 'java', run: (fixture: { directory: string; checkout: string; sha: string; model: string; snapshot: string; catalog: string; discovery: string; env: NodeJS.ProcessEnv; invoke: (script: 'protocol' | 'tools', args?: string[], env?: NodeJS.ProcessEnv) => SpawnSyncReturns; -}) => void) { +}) => void, javaLayout: 'catalog' | 'toolsets' = 'catalog') { const directory = mkdtempSync(join(tmpdir(), 'libtmux-mcp-source-')) const checkout = join(directory, 'source') const git = (...args: string[]) => execFileSync('git', ['-C', checkout, ...args], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim() @@ -31,8 +31,15 @@ function fixture(slug: 'go' | 'py' | 'ruby', run: (fixture: { copyFileSync(join(root, file), target) } write(join(directory, 'package.json'), '{"type":"module"}') - write(join(checkout, slug === 'go' ? 'mcp/manifest_catalog.go' : 'src/libtmux_mcp/tools/sessions.py'), - slug === 'go' ? 'var catalog = []tool{{name: "list_sessions"}}\n' : 'mcp.tool()(list_sessions)\n') + if (slug === 'java') { + const directory = join(checkout, 'libtmux-mcp/src/main/java/io/github/libtmux/mcp') + write(join(directory, javaLayout === 'catalog' ? 'Catalog.java' : 'InspectTools.java'), + `Catalog.tool("helper_only");\ntools.add(${javaLayout === 'catalog' ? 'inspect(' : 'Catalog.literalized(Catalog.tool('}"list_sessions")${javaLayout === 'catalog' ? ')' : '))'};\n`) + write(join(directory, 'CatalogTest.java'), 'tools.add(tool("test_only"));\n') + } else { + write(join(checkout, slug === 'go' ? 'mcp/manifest_catalog.go' : 'src/libtmux_mcp/tools/sessions.py'), + slug === 'go' ? 'var catalog = []tool{{name: "list_sessions"}}\n' : 'mcp.tool()(list_sessions)\n') + } if (slug === 'py') write(join(checkout, 'docs/tools/list-sessions.md'), '# List sessions\n') git('init', '-q') git('add', '.') @@ -108,6 +115,24 @@ it.each(['go', 'py'] as const)('binds the %s catalog to its actual MCP source an }) }) +it.each(['catalog', 'toolsets'] as const)('binds Java %s registrations to runtime schemas and their source lines', (layout) => { + fixture('java', ({ sha, catalog, invoke }) => { + const captured = invoke('protocol') + expect(captured.status, captured.stderr).toBe(0) + const generated = invoke('tools') + expect(generated.status, generated.stderr).toBe(0) + const result = JSON.parse(readFileSync(catalog, 'utf8')).ports.java + expect(result.tools).toEqual(['list_sessions']) + expect(result.registrations).toEqual([expect.objectContaining({ + ...tool, schemaStatus: 'runtime', source: { + repo: 'libtmux/libtmux-java', revision: sha, extractedRevision: sha, + file: `libtmux-mcp/src/main/java/io/github/libtmux/mcp/${layout === 'catalog' ? 'Catalog' : 'InspectTools'}.java`, + line: 2, + }, + })]) + }, layout) +}) + it.each(['core revision', 'MCP revision', 'MCP repository'])('rejects an API model with the wrong %s before starting the server', (field) => { fixture('go', ({ model, discovery, invoke }) => { const value = JSON.parse(readFileSync(model, 'utf8')) From c3932f420be2765bbc823ad72d21cf7c07b13f2e Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Wed, 30 Sep 2026 16:47:25 -0500 Subject: [PATCH 2/2] ci(docs) Install the pinned Swift compiler with mise why: The setup-swift v2 release catalog rejects Swift 6.2.4 before the reference build starts. The selected source already pins that compiler. what: - Use mise-action v5.0.0 at its verified commit with mise 2026.9.17 - Install only the selected Swift version with GPG verification enabled - Disable caches, shims and unrelated environment exports - Verify the compiler version before adding its bin directory to PATH Validation: version checks reject a wrong or unreadable version. The installed 6.2.4 passes. The outer docs loop passes in 48.5 seconds. Actionlint retains the same eight unsupported GitHub job-context diagnostics; hosted installation and publication remain to be verified. Releases: https://github.com/jdx/mise-action/releases/tag/v5.0.0 https://github.com/jdx/mise/releases/tag/v2026.9.17 --- .github/workflows/port-docs.yml | 28 +++++++++++++++++++++++++--- site/test/port-docs-runtime.test.ts | 24 ++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/.github/workflows/port-docs.yml b/.github/workflows/port-docs.yml index c82943c5..4b7f165b 100644 --- a/.github/workflows/port-docs.yml +++ b/.github/workflows/port-docs.yml @@ -367,10 +367,32 @@ jobs: # for the same reason and with the same freshness check. The resolved # versions are forced so the build neither re-resolves nor rewrites # Package.resolved. - - if: matrix.port == 'swift' - uses: swift-actions/setup-swift@7ca6abe6b3b0e8b5421b88be48feee39cbf52c6a # v2 + - name: Install selected Swift toolchain + if: matrix.port == 'swift' + uses: jdx/mise-action@9149ea85001c7435d5a66bb127d6a1b6227cb0a5 # v5.0.0 + env: + MISE_SWIFT_GPG_VERIFY: 'true' with: - swift-version: ${{ steps.native-version.outputs.version }} + version: '2026.9.17' + install_args: swift@${{ steps.native-version.outputs.version }} + cache: false + env: false + export_path: false + add_shims_to_path: false + - name: Verify selected Swift toolchain + if: matrix.port == 'swift' + env: + SWIFT_VERSION: ${{ steps.native-version.outputs.version }} + run: | + set -euo pipefail + swift_bin=$(mise where "swift@$SWIFT_VERSION")/bin + actual=$("$swift_bin/swift" --version | sed -nE 's/^(Apple )?Swift version ([0-9.]+).*$/\2/p') + [[ "$actual" == "$SWIFT_VERSION" ]] || { + echo "::error::Expected Swift $SWIFT_VERSION, got ${actual:-no version}" >&2 + exit 1 + } + echo "Swift $actual: $swift_bin" + printf '%s\n' "$swift_bin" >> "$GITHUB_PATH" - name: Emit the Swift symbol graph if: matrix.port == 'swift' working-directory: port diff --git a/site/test/port-docs-runtime.test.ts b/site/test/port-docs-runtime.test.ts index f6f45bd3..59efb375 100644 --- a/site/test/port-docs-runtime.test.ts +++ b/site/test/port-docs-runtime.test.ts @@ -72,6 +72,30 @@ describe('selected MCP runtime provisioning', () => { } finally { rmSync(directory, { recursive: true, force: true }) } }) + it.each(['6.2.4', '6.2.3', 'unrecognized'])('verifies the installed Swift compiler reports %s before adding it to PATH', (version) => { + const directory = mkdtempSync(join(tmpdir(), 'libtmux-swift-version-')) + try { + const bin = join(directory, 'bin') + const output = join(directory, 'path') + mkdirSync(bin) + writeFileSync(output, '') + writeFileSync(join(bin, 'mise'), '#!/bin/sh\nprintf "%s\\n" "$STUB_TOOLCHAIN"\n', { mode: 0o700 }) + writeFileSync(join(bin, 'swift'), `#!/bin/sh\nprintf '%s\\n' 'Swift version ${version} (fixture)'\n`, { mode: 0o700 }) + const result = spawnSync('bash', ['-e', '-c', script('Verify selected Swift toolchain')], { + cwd: directory, encoding: 'utf8', timeout: 5000, + env: { ...process.env, PATH: `${bin}:${process.env.PATH}`, STUB_TOOLCHAIN: directory, SWIFT_VERSION: '6.2.4', GITHUB_PATH: output }, + }) + if (version === '6.2.4') { + expect(result.status, result.stderr).toBe(0) + expect(readFileSync(output, 'utf8')).toBe(`${bin}\n`) + } else { + expect(result.status).not.toBe(0) + expect(result.stderr).toContain('Expected Swift 6.2.4') + expect(readFileSync(output, 'utf8')).toBe('') + } + } finally { rmSync(directory, { recursive: true, force: true }) } + }) + it('installs the separate Python MCP checkout without altering the captured inputs', () => { const snapshot = workflow.indexOf('name: Snapshot source inputs before native generation') const runtime = workflow.indexOf("name: Install Python's separate MCP runtime")