diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml index 45f17f2b8..85141981f 100644 --- a/.github/workflows/pre-release.yml +++ b/.github/workflows/pre-release.yml @@ -3,6 +3,7 @@ name: Pre-release permissions: read-all on: + workflow_dispatch: push: branches: - release-please-* @@ -23,10 +24,5 @@ jobs: cache: npm node-version-file: '.nvmrc' - - name: Install MCP Publisher - run: | - export OS=$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/') - curl -L "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_${OS}.tar.gz" | tar xz mcp-publisher - - name: Verify server.json run: npm run verify-server-json-version diff --git a/GEMINI.md b/GEMINI.md index 34d21fc0f..d0eac03eb 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -1,4 +1,5 @@ # Instructions -- use `npm run build` to run tsc and test build -- use `npm run test` to run tests, run all tests to verify correctness +- use `npm run build` to run tsc and test build. +- use `npm run test` to run tests, run all tests to verify correctness. +- use only scripts from `package.json` to run commands. diff --git a/eslint.config.mjs b/eslint.config.mjs index 04882b055..db6ddd675 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -60,6 +60,7 @@ export default defineConfig([ name: 'TypeScript rules', rules: { '@local/check-license': 'error', + curly: ['error', 'all'], 'no-undef': 'off', 'no-unused-vars': 'off', diff --git a/scripts/generate-docs.ts b/scripts/generate-docs.ts index 9e59bdaf5..3294fd4ab 100644 --- a/scripts/generate-docs.ts +++ b/scripts/generate-docs.ts @@ -213,15 +213,23 @@ function getZodTypeInfo(schema: ZodSchema): TypeInfo { defaultValue = def.defaultValue(); } const next = def.innerType || def.schema; - if (!next) break; + if (!next) { + break; + } schema = next; def = schema._def; - if (!description && schema.description) description = schema.description; + if (!description && schema.description) { + description = schema.description; + } } const result: TypeInfo = {type: 'unknown'}; - if (description) result.description = description; - if (defaultValue !== undefined) result.default = defaultValue; + if (description) { + result.description = description; + } + if (defaultValue !== undefined) { + result.default = defaultValue; + } switch (def.typeName) { case 'ZodString': @@ -254,7 +262,9 @@ function getZodTypeInfo(schema: ZodSchema): TypeInfo { function isRequired(schema: ZodSchema): boolean { let def = schema._def; while (def.typeName === 'ZodEffects') { - if (!def.schema) break; + if (!def.schema) { + break; + } schema = def.schema; def = schema._def; } @@ -325,9 +335,15 @@ async function generateToolDocumentation(): Promise { const aIndex = categoryOrder.indexOf(a); const bIndex = categoryOrder.indexOf(b); // Put known categories first, unknown categories last - if (aIndex === -1 && bIndex === -1) return a.localeCompare(b); - if (aIndex === -1) return 1; - if (bIndex === -1) return -1; + if (aIndex === -1 && bIndex === -1) { + return a.localeCompare(b); + } + if (aIndex === -1) { + return 1; + } + if (bIndex === -1) { + return -1; + } return aIndex - bIndex; }); @@ -386,8 +402,12 @@ async function generateToolDocumentation(): Promise { const propertyNames = Object.keys(properties).sort((a, b) => { const aRequired = required.includes(a); const bRequired = required.includes(b); - if (aRequired && !bRequired) return -1; - if (!aRequired && bRequired) return 1; + if (aRequired && !bRequired) { + return -1; + } + if (!aRequired && bRequired) { + return 1; + } return a.localeCompare(b); }); for (const propName of propertyNames) { diff --git a/scripts/verify-server-json-version.ts b/scripts/verify-server-json-version.ts index 7ffa6c5f0..81deb147a 100644 --- a/scripts/verify-server-json-version.ts +++ b/scripts/verify-server-json-version.ts @@ -6,31 +6,72 @@ import {execSync} from 'node:child_process'; import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; -const serverJsonFilePath = './server.json'; +const serverJsonFilePath = path.join(process.cwd(), 'server.json'); const serverJson = JSON.parse(fs.readFileSync(serverJsonFilePath, 'utf-8')); -fs.unlinkSync(serverJsonFilePath); -// Create the new server.json -execSync('./mcp-publisher init'); +const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mcp-verify-')); -const newServerJson = JSON.parse(fs.readFileSync(serverJsonFilePath, 'utf-8')); - -const propertyToVerify = ['$schema']; -const diffProps = []; +try { + const osName = os.platform(); + const arch = os.arch(); + let platform = ''; + if (osName === 'darwin') { + platform = 'darwin'; + } else if (osName === 'linux') { + platform = 'linux'; + } + // mcp-publisher does not support windows + else { + throw new Error(`Unsupported platform: ${osName}`); + } -for (const prop of propertyToVerify) { - if (serverJson[prop] !== newServerJson[prop]) { - diffProps.push(prop); + let archName = ''; + if (arch === 'x64') { + archName = 'amd64'; + } else if (arch === 'arm64') { + archName = 'arm64'; + } else { + throw new Error(`Unsupported architecture: ${arch}`); } -} -fs.writeFileSync('./server.json', JSON.stringify(serverJson, null, 2)); + const osArch = `${platform}_${archName}`; + const binName = 'mcp-publisher'; + const downloadUrl = `https://github.com/modelcontextprotocol/registry/releases/latest/download/${binName}_${osArch}.tar.gz`; + + console.log(`Downloading ${binName} from ${downloadUrl}`); + const downloadCmd = `curl -L "${downloadUrl}" | tar xz -C "${tmpDir}" ${binName}`; + execSync(downloadCmd, {stdio: 'inherit'}); + + const publisherPath = path.join(tmpDir, binName); + fs.chmodSync(publisherPath, 0o755); + console.log(`Downloaded to ${publisherPath}`); -if (diffProps.length) { - throw new Error( - `The following props did not match the latest init value:\n${diffProps.map( - prop => `- "${prop}": "${newServerJson[prop]}"`, - )}`, - ); + // Create the new server.json in the temporary directory + execSync(`${publisherPath} init`, {cwd: tmpDir, stdio: 'inherit'}); + + const newServerJsonPath = path.join(tmpDir, 'server.json'); + const newServerJson = JSON.parse(fs.readFileSync(newServerJsonPath, 'utf-8')); + + const propertyToVerify = ['$schema']; + const diffProps = []; + + for (const prop of propertyToVerify) { + if (serverJson[prop] !== newServerJson[prop]) { + diffProps.push(prop); + } + } + + if (diffProps.length) { + throw new Error( + `The following props in ${serverJsonFilePath} did not match the latest init value:\n${diffProps.map( + prop => + `- "${prop}": expected "${newServerJson[prop]}", got "${serverJson[prop]}"`, + )}`, + ); + } +} finally { + fs.rmSync(tmpDir, {recursive: true, force: true}); } diff --git a/server.json b/server.json index abb84deca..8c66326e2 100644 --- a/server.json +++ b/server.json @@ -1,5 +1,5 @@ { - "$schema": "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json", + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", "name": "io.github.ChromeDevTools/chrome-devtools-mcp", "title": "Chrome DevTools MCP", "description": "MCP server for Chrome DevTools", diff --git a/src/McpResponse.ts b/src/McpResponse.ts index edb8c95ba..9c859d43b 100644 --- a/src/McpResponse.ts +++ b/src/McpResponse.ts @@ -259,10 +259,11 @@ export class McpResponse implements Response { }; } else if (message instanceof DevTools.AggregatedIssue) { const mappedIssueMessage = mapIssueToMessageObject(message); - if (!mappedIssueMessage) + if (!mappedIssueMessage) { throw new Error( "Can't provide detals for the msgid " + consoleMessageStableId, ); + } consoleData = { consoleMessageStableId, ...mappedIssueMessage, @@ -321,7 +322,9 @@ export class McpResponse implements Response { } if (item instanceof DevTools.AggregatedIssue) { const mappedIssueMessage = mapIssueToMessageObject(item); - if (!mappedIssueMessage) return null; + if (!mappedIssueMessage) { + return null; + } return { consoleMessageStableId, ...mappedIssueMessage, diff --git a/src/formatters/consoleFormatter.ts b/src/formatters/consoleFormatter.ts index 06071c197..fdfa8a361 100644 --- a/src/formatters/consoleFormatter.ts +++ b/src/formatters/consoleFormatter.ts @@ -84,7 +84,9 @@ export function formatIssue( if (processedMarkdown?.startsWith('# ')) { processedMarkdown = processedMarkdown.substring(2).trimStart(); } - if (processedMarkdown) result.push(processedMarkdown); + if (processedMarkdown) { + result.push(processedMarkdown); + } const links = issue.getDescription()?.links; if (links && links.length > 0) { @@ -102,7 +104,9 @@ export function formatIssue( }> = []; for (const singleIssue of issues) { const details = singleIssue.details(); - if (!details) continue; + if (!details) { + continue; + } // We send the remaining details as untyped JSON because the DevTools // frontend code is currently not re-usable. @@ -152,17 +156,23 @@ export function formatIssue( result.push( ...affectedResources.map(item => { const details = []; - if (item.uid) details.push(`uid=${item.uid}`); + if (item.uid) { + details.push(`uid=${item.uid}`); + } if (item.request) { details.push( (typeof item.request === 'number' ? `reqid=` : 'url=') + item.request, ); } - if (item.data) details.push(`data=${JSON.stringify(item.data)}`); + if (item.data) { + details.push(`data=${JSON.stringify(item.data)}`); + } return details.join(' '); }), ); - if (result.length === 0) return 'No affected resources found'; + if (result.length === 0) { + return 'No affected resources found'; + } return result.join('\n'); }