Skip to content

Commit b707e08

Browse files
author
ci-bot
committed
code analysis tools
1 parent 6987377 commit b707e08

File tree

10 files changed

+290
-94
lines changed

10 files changed

+290
-94
lines changed

libs/remix-ai-core/src/remix-mcp-server/RemixMCPServer.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { createCompilationTools } from './handlers/CompilationHandler';
2929
import { createFileManagementTools } from './handlers/FileManagementHandler';
3030
import { createDeploymentTools } from './handlers/DeploymentHandler';
3131
import { createDebuggingTools } from './handlers/DebuggingHandler';
32+
import { createCodeAnalysisTools } from './handlers/CodeAnalysisHandler';
3233

3334
// Import resource providers
3435
import { ProjectResourceProvider } from './providers/ProjectResourceProvider';
@@ -468,6 +469,11 @@ export class RemixMCPServer extends EventEmitter implements IRemixMCPServer {
468469
this._tools.registerBatch(debuggingTools);
469470
console.log(`Registered ${debuggingTools.length} debugging tools`, 'info');
470471

472+
// Register debugging tools
473+
const codeAnalysisTools = createCodeAnalysisTools();
474+
this._tools.registerBatch(codeAnalysisTools);
475+
console.log(`Registered ${codeAnalysisTools.length} code analysis tools`, 'info');
476+
471477
const totalTools = this._tools.list().length;
472478
console.log(`Total tools registered: ${totalTools}`, 'info');
473479

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/**
2+
* Code Analysis Tool Handlers for Remix MCP Server
3+
*/
4+
5+
import { IMCPToolResult } from '../../types/mcp';
6+
import { BaseToolHandler } from '../registry/RemixToolRegistry';
7+
import {
8+
ToolCategory,
9+
RemixToolDefinition
10+
} from '../types/mcpTools';
11+
import { Plugin } from '@remixproject/engine';
12+
import { performSolidityScan } from '@remix-project/core-plugin';
13+
14+
/**
15+
* Solidity Scan Tool Handler
16+
* Analyzes Solidity code for security vulnerabilities and code quality issues
17+
*/
18+
export class SolidityScanHandler extends BaseToolHandler {
19+
name = 'solidity_scan';
20+
description = 'Scan Solidity smart contracts for security vulnerabilities and code quality issues using SolidityScan API';
21+
inputSchema = {
22+
type: 'object',
23+
properties: {
24+
filePath: {
25+
type: 'string',
26+
description: 'Path to the Solidity file to scan (relative to workspace root)'
27+
}
28+
},
29+
required: ['filePath']
30+
};
31+
32+
getPermissions(): string[] {
33+
return ['analysis:scan', 'file:read'];
34+
}
35+
36+
validate(args: { filePath: string }): boolean | string {
37+
const required = this.validateRequired(args, ['filePath']);
38+
if (required !== true) return required;
39+
40+
const types = this.validateTypes(args, {
41+
filePath: 'string'
42+
});
43+
if (types !== true) return types;
44+
45+
if (!args.filePath.endsWith('.sol')) {
46+
return 'File must be a Solidity file (.sol)';
47+
}
48+
49+
return true;
50+
}
51+
52+
async execute(args: { filePath: string }, plugin: Plugin): Promise<IMCPToolResult> {
53+
try {
54+
// Check if file exists
55+
const workspace = await plugin.call('filePanel', 'getCurrentWorkspace');
56+
const fileName = `${workspace.name}/${args.filePath}`;
57+
const filePath = `.workspaces/${fileName}`;
58+
59+
const exists = await plugin.call('fileManager', 'exists', filePath);
60+
if (!exists) {
61+
return this.createErrorResult(`File not found: ${args.filePath}`);
62+
}
63+
64+
// Use the core scanning function from remix-core-plugin
65+
const scanReport = await performSolidityScan(plugin, args.filePath);
66+
67+
// Process scan results into structured format
68+
const findings = [];
69+
70+
for (const template of scanReport.multi_file_scan_details || []) {
71+
if (template.metric_wise_aggregated_findings?.length) {
72+
for (const details of template.metric_wise_aggregated_findings) {
73+
for (const finding of details.findings) {
74+
findings.push({
75+
metric: details.metric_name,
76+
severity: details.severity || 'unknown',
77+
title: finding.title || details.metric_name,
78+
description: finding.description || details.description,
79+
lineStart: finding.line_nos_start?.[0],
80+
lineEnd: finding.line_nos_end?.[0],
81+
file: template.file_name,
82+
recommendation: finding.recommendation
83+
});
84+
}
85+
}
86+
}
87+
}
88+
89+
const result = {
90+
success: true,
91+
fileName,
92+
scanCompletedAt: new Date().toISOString(),
93+
totalFindings: findings.length,
94+
findings,
95+
summary: {
96+
critical: findings.filter(f => f.severity === 'critical').length,
97+
high: findings.filter(f => f.severity === 'high').length,
98+
medium: findings.filter(f => f.severity === 'medium').length,
99+
low: findings.filter(f => f.severity === 'low').length,
100+
informational: findings.filter(f => f.severity === 'informational').length
101+
}
102+
};
103+
104+
return this.createSuccessResult(result);
105+
106+
} catch (error) {
107+
return this.createErrorResult(`Scan failed: ${error.message}`);
108+
}
109+
}
110+
}
111+
112+
/**
113+
* Create code analysis tool definitions
114+
*/
115+
export function createCodeAnalysisTools(): RemixToolDefinition[] {
116+
return [
117+
{
118+
name: 'solidity_scan',
119+
description: 'Scan Solidity smart contracts for security vulnerabilities and code quality issues using SolidityScan API',
120+
inputSchema: new SolidityScanHandler().inputSchema,
121+
category: ToolCategory.ANALYSIS,
122+
permissions: ['analysis:scan', 'file:read'],
123+
handler: new SolidityScanHandler()
124+
}
125+
];
126+
}

libs/remix-ai-core/src/remix-mcp-server/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export { createFileManagementTools } from './handlers/FileManagementHandler';
1616
export { createCompilationTools } from './handlers/CompilationHandler';
1717
export { createDeploymentTools } from './handlers/DeploymentHandler';
1818
export { createDebuggingTools } from './handlers/DebuggingHandler';
19+
export { createCodeAnalysisTools } from './handlers/CodeAnalysisHandler';
1920

2021
// Resource Providers
2122
export { ProjectResourceProvider } from './providers/ProjectResourceProvider';

libs/remix-core-plugin/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ export { LinkLibraries, DeployLibraries } from './lib/link-libraries'
99
export { OpenZeppelinProxy } from './lib/openzeppelin-proxy'
1010
export { fetchContractFromEtherscan } from './lib/helpers/fetch-etherscan'
1111
export { fetchContractFromBlockscout } from './lib/helpers/fetch-blockscout'
12+
export { performSolidityScan, handleSolidityScan, type ScanReportRenderer } from './lib/solidity-scan'
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import axios from 'axios'
2+
import { endpointUrls } from '@remix-endpoints-helper'
3+
import { ScanReport } from '@remix-ui/helper'
4+
5+
const _paq = (window._paq = window._paq || [])
6+
7+
/**
8+
* Core function to perform Solidity scan and return the scan report
9+
* @param api - Remix API instance
10+
* @param compiledFileName - Name of the file to scan
11+
* @returns Promise with the scan report or throws error
12+
*/
13+
export const performSolidityScan = async (api: any, compiledFileName: string): Promise<ScanReport> => {
14+
const workspace = await api.call('filePanel', 'getCurrentWorkspace')
15+
const fileName = `${workspace.name}/${compiledFileName}`
16+
const filePath = `.workspaces/${fileName}`
17+
const file = await api.call('fileManager', 'readFile', filePath)
18+
19+
const urlResponse = await axios.post(`${endpointUrls.solidityScan}/uploadFile`, { file, fileName })
20+
21+
if (urlResponse.data.status !== 'success') {
22+
throw new Error(urlResponse.data.error || 'Failed to upload file to SolidityScan')
23+
}
24+
25+
return new Promise((resolve, reject) => {
26+
const ws = new WebSocket(`${endpointUrls.solidityScanWebSocket}/solidityscan`)
27+
28+
const timeout = setTimeout(() => {
29+
ws.close()
30+
reject(new Error('Scan timeout'))
31+
}, 300000) // 5 minute timeout
32+
33+
ws.addEventListener('error', (error) => {
34+
clearTimeout(timeout)
35+
reject(new Error('WebSocket connection failed'))
36+
})
37+
38+
ws.addEventListener('message', async (event) => {
39+
try {
40+
const data = JSON.parse(event.data)
41+
42+
if (data.type === "auth_token_register" && data.payload.message === "Auth token registered.") {
43+
ws.send(JSON.stringify({
44+
action: "message",
45+
payload: {
46+
type: "private_project_scan_initiate",
47+
body: {
48+
file_urls: [urlResponse.data.result.url],
49+
project_name: "RemixProject",
50+
project_type: "new"
51+
}
52+
}
53+
}))
54+
} else if (data.type === "scan_status" && data.payload.scan_status === "download_failed") {
55+
clearTimeout(timeout)
56+
ws.close()
57+
reject(new Error(data.payload.scan_status_err_message || 'Scan failed'))
58+
} else if (data.type === "scan_status" && data.payload.scan_status === "scan_done") {
59+
clearTimeout(timeout)
60+
const { data: scanData } = await axios.post(`${endpointUrls.solidityScan}/downloadResult`, { url: data.payload.scan_details.link })
61+
const scanReport: ScanReport = scanData.scan_report
62+
63+
if (scanReport?.multi_file_scan_details?.length) {
64+
// Process positions for each template
65+
for (const template of scanReport.multi_file_scan_details) {
66+
if (template.metric_wise_aggregated_findings?.length) {
67+
const positions = []
68+
for (const details of template.metric_wise_aggregated_findings) {
69+
for (const f of details.findings)
70+
positions.push(`${f.line_nos_start[0]}:${f.line_nos_end[0]}`)
71+
}
72+
template.positions = JSON.stringify(positions)
73+
}
74+
}
75+
ws.close()
76+
resolve(scanReport)
77+
} else {
78+
ws.close()
79+
reject(new Error('No scan results found'))
80+
}
81+
}
82+
} catch (error) {
83+
clearTimeout(timeout)
84+
ws.close()
85+
reject(error)
86+
}
87+
})
88+
})
89+
}
90+
91+
/**
92+
* Callback type for rendering scan results
93+
* @param scanReport - The scan report to render
94+
* @param fileName - The name of the scanned file
95+
* @returns JSX element or any renderable content for the terminal
96+
*/
97+
export type ScanReportRenderer = (scanReport: ScanReport, fileName: string) => any
98+
99+
/**
100+
* Handler for Solidity scan with notifications and terminal output
101+
* @param api - Remix API instance
102+
* @param compiledFileName - Name of the file to scan
103+
* @param modalMessage - Error modal title message
104+
* @param renderResults - Callback function to render the scan results (e.g., as JSX)
105+
*/
106+
export const handleSolidityScan = async (
107+
api: any,
108+
compiledFileName: string,
109+
modalMessage: string,
110+
renderResults: ScanReportRenderer
111+
) => {
112+
await api.call('notification', 'toast', 'Processing data to scan...')
113+
_paq.push(['trackEvent', 'solidityCompiler', 'solidityScan', 'initiateScan'])
114+
115+
try {
116+
const workspace = await api.call('filePanel', 'getCurrentWorkspace')
117+
const fileName = `${workspace.name}/${compiledFileName}`
118+
119+
await api.call('notification', 'toast', 'Loading scan result in Remix terminal...')
120+
121+
const scanReport = await performSolidityScan(api, compiledFileName)
122+
123+
_paq.push(['trackEvent', 'solidityCompiler', 'solidityScan', 'scanSuccess'])
124+
const renderedResults = renderResults(scanReport, fileName)
125+
await api.call('terminal', 'logHtml', renderedResults)
126+
} catch (error) {
127+
_paq.push(['trackEvent', 'solidityCompiler', 'solidityScan', 'scanFailed'])
128+
await api.call('notification', 'modal', {
129+
id: 'SolidityScanError',
130+
title: modalMessage,
131+
message: error.message || 'Some error occurred! Please try again',
132+
okLabel: 'Close'
133+
})
134+
console.error(error)
135+
}
136+
}

libs/remix-ui/helper/src/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,5 @@ export * from './lib/components/custom-dropdown'
66
export * from './lib/components/custom-tooltip'
77
export * from './lib/components/feedback'
88
export * from './lib/components/solScanTable'
9-
export * from './lib/solidity-scan'
109
export type { CompilerReport } from './types/compilerTypes'
1110
export * from './types/solidity-scan'

libs/remix-ui/helper/src/lib/solidity-scan.tsx

Lines changed: 0 additions & 88 deletions
This file was deleted.

0 commit comments

Comments
 (0)