Skip to content

Commit a071c15

Browse files
authored
Merge pull request #6424 from remix-project-org/deploy_mcp
mcp: deploy and interact
2 parents 436dc6d + 9f6081c commit a071c15

File tree

32 files changed

+1115
-363
lines changed

32 files changed

+1115
-363
lines changed

apps/remix-ide/src/app/udapp/run-tab.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,9 @@ const profile = {
5656
'resolveContractAndAddInstance',
5757
'showPluginDetails',
5858
'getRunTabAPI',
59-
'getDeployedContracts'
59+
'getDeployedContracts',
60+
'getAllDeployedInstances',
61+
'setAccount'
6062
]
6163
}
6264

@@ -151,6 +153,14 @@ export class RunTab extends ViewPlugin {
151153
}
152154
}
153155

156+
setAccount(address: string) {
157+
this.emit('setAccountReducer', address)
158+
}
159+
160+
getAllDeployedInstances() {
161+
return this.REACT_API.instances?.instanceList
162+
}
163+
154164
clearAllInstances() {
155165
this.emit('clearAllInstancesReducer')
156166
this.transactionHistory.clear()

apps/remix-ide/src/blockchain/blockchain.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ const profile = {
2525
name: 'blockchain',
2626
displayName: 'Blockchain',
2727
description: 'Blockchain - Logic',
28-
methods: ['dumpState', 'getCode', 'getTransactionReceipt', 'addProvider', 'removeProvider', 'getCurrentFork', 'isSmartAccount', 'getAccounts', 'web3VM', 'web3', 'getProvider', 'getCurrentProvider', 'getCurrentNetworkStatus', 'getCurrentNetworkCurrency', 'getAllProviders', 'getPinnedProviders', 'changeExecutionContext', 'getProviderObject', 'runTx', 'getBalanceInEther', 'getCurrentProvider'],
28+
methods: ['dumpState', 'getCode', 'getTransactionReceipt', 'addProvider', 'removeProvider', 'getCurrentFork', 'isSmartAccount', 'getAccounts', 'web3VM', 'web3', 'getProvider', 'getCurrentProvider', 'getCurrentNetworkStatus', 'getCurrentNetworkCurrency', 'getAllProviders', 'getPinnedProviders', 'changeExecutionContext', 'getProviderObject', 'runTx', 'getBalanceInEther', 'getCurrentProvider', 'deployContractAndLibraries', 'runOrCallContractMethod'],
2929

3030
version: packageJson.version
3131
}
@@ -303,7 +303,9 @@ export class Blockchain extends Plugin {
303303
args,
304304
(error, data) => {
305305
if (error) {
306-
return statusCb(`creation of ${selectedContract.name} errored: ${error.message ? error.message : error.error ? error.error : error}`)
306+
statusCb(`creation of ${selectedContract.name} errored: ${error.message ? error.message : error.error ? error.error : error}`)
307+
finalCb(error)
308+
return
307309
}
308310

309311
statusCb(`creation of ${selectedContract.name} pending...`)
@@ -546,7 +548,7 @@ export class Blockchain extends Plugin {
546548
if (txResult.receipt.status === false || txResult.receipt.status === '0x0' || txResult.receipt.status === 0) {
547549
return finalCb(`creation of ${selectedContract.name} errored: transaction execution failed`)
548550
}
549-
finalCb(null, selectedContract, address)
551+
finalCb(null, selectedContract, address, txResult)
550552
})
551553
}
552554

@@ -607,7 +609,7 @@ export class Blockchain extends Plugin {
607609
}
608610

609611
changeExecutionContext(context, confirmCb, infoCb, cb) {
610-
if (this.currentRequest && this.currentRequest.from && !this.currentRequest.from.startsWith('injected')) {
612+
if (this.currentRequest && this.currentRequest.from && !this.currentRequest.from.startsWith('injected') && this.currentRequest.from !== 'remixAI') {
611613
// only injected provider can update the provider.
612614
return
613615
}
@@ -668,7 +670,7 @@ export class Blockchain extends Plugin {
668670
return txlistener
669671
}
670672

671-
runOrCallContractMethod(contractName, contractAbi, funABI, contract, value, address, callType, lookupOnly, logMsg, logCallback, outputCb, confirmationCb, continueCb, promptCb) {
673+
runOrCallContractMethod(contractName, contractAbi, funABI, contract, value, address, callType, lookupOnly, logMsg, logCallback, outputCb, confirmationCb, continueCb, promptCb, finalCb) {
672674
// contractsDetails is used to resolve libraries
673675
txFormat.buildData(
674676
contractName,
@@ -701,6 +703,7 @@ export class Blockchain extends Plugin {
701703
if (lookupOnly) {
702704
outputCb(returnValue)
703705
}
706+
if (finalCb) finalCb(error, {txResult, address: _address, returnValue})
704707
})
705708
},
706709
(msg) => {

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

Lines changed: 9 additions & 3 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';
@@ -464,9 +465,14 @@ export class RemixMCPServer extends EventEmitter implements IRemixMCPServer {
464465
console.log(`Registered ${deploymentTools.length} deployment tools`, 'info');
465466

466467
// Register debugging tools
467-
// const debuggingTools = createDebuggingTools();
468-
// this._tools.registerBatch(debuggingTools);
469-
// console.log(`Registered ${debuggingTools.length} debugging tools`, 'info');
468+
const debuggingTools = createDebuggingTools();
469+
this._tools.registerBatch(debuggingTools);
470+
console.log(`Registered ${debuggingTools.length} debugging tools`, 'info');
471+
472+
// Register debugging tools
473+
const codeAnalysisTools = createCodeAnalysisTools();
474+
this._tools.registerBatch(codeAnalysisTools);
475+
console.log(`Registered ${codeAnalysisTools.length} code analysis tools`, 'info');
470476

471477
const totalTools = this._tools.list().length;
472478
console.log(`Total tools registered: ${totalTools}`, 'info');
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/handlers/DebuggingHandler.ts

Lines changed: 6 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -30,49 +30,35 @@ export class StartDebugSessionHandler extends BaseToolHandler {
3030
inputSchema = {
3131
type: 'object',
3232
properties: {
33-
contractAddress: {
34-
type: 'string',
35-
description: 'Contract address to debug',
36-
pattern: '^0x[a-fA-F0-9]{40}$'
37-
},
3833
transactionHash: {
3934
type: 'string',
4035
description: 'Transaction hash to debug (optional)',
4136
pattern: '^0x[a-fA-F0-9]{64}$'
4237
},
43-
sourceFile: {
44-
type: 'string',
45-
description: 'Source file path to debug'
46-
},
38+
/*
4739
network: {
4840
type: 'string',
4941
description: 'Network to debug on',
5042
default: 'local'
5143
}
44+
*/
5245
},
53-
required: ['contractAddress']
46+
required: ['transactionHash']
5447
};
5548

5649
getPermissions(): string[] {
5750
return ['debug:start'];
5851
}
5952

6053
validate(args: DebugSessionArgs): boolean | string {
61-
const required = this.validateRequired(args, ['contractAddress']);
54+
const required = this.validateRequired(args, ['transactionHash']);
6255
if (required !== true) return required;
6356

6457
const types = this.validateTypes(args, {
65-
contractAddress: 'string',
6658
transactionHash: 'string',
67-
sourceFile: 'string',
68-
network: 'string'
6959
});
7060
if (types !== true) return types;
7161

72-
if (!args.contractAddress.match(/^0x[a-fA-F0-9]{40}$/)) {
73-
return 'Invalid contract address format';
74-
}
75-
7662
if (args.transactionHash && !args.transactionHash.match(/^0x[a-fA-F0-9]{64}$/)) {
7763
return 'Invalid transaction hash format';
7864
}
@@ -82,21 +68,15 @@ export class StartDebugSessionHandler extends BaseToolHandler {
8268

8369
async execute(args: DebugSessionArgs, plugin: Plugin): Promise<IMCPToolResult> {
8470
try {
85-
// TODO: Integrate with Remix debugger plugin
86-
const sessionId = 'debug_' + Date.now();
87-
71+
await plugin.call('debugger', 'debug', args.transactionHash)
8872
// Mock debug session creation
8973
const result: DebugSessionResult = {
9074
success: true,
91-
sessionId,
92-
contractAddress: args.contractAddress,
93-
network: args.network || 'local',
9475
transactionHash: args.transactionHash,
95-
sourceFile: args.sourceFile,
9676
status: 'started',
9777
createdAt: new Date().toISOString()
9878
};
99-
79+
plugin.call('menuicons', 'select', 'debugger')
10080
return this.createSuccessResult(result);
10181

10282
} catch (error) {

0 commit comments

Comments
 (0)