Algorand MCP
by GoPlausible
import algosdk, { modelsv2 } from 'algosdk';
import type { EncodedSignedTransaction } from 'algosdk';
import type {
SimulateResponse,
CompileResponse,
DisassembleResponse,
SimulateTraceConfig,
PostTransactionsResponse
} from 'algosdk/dist/types/client/v2/algod/models/types';
import { algodClient } from '../algorand-client.js';
import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
// Tool schemas
export const algodToolSchemas = {
compileTeal: {
type: 'object',
properties: {
source: { type: 'string' }
},
required: ['source']
},
disassembleTeal: {
type: 'object',
properties: {
bytecode: { type: 'string' }
},
required: ['bytecode']
},
sendRawTransaction: {
type: 'object',
properties: {
signedTxns: {
type: 'array',
items: { type: 'string' }
}
},
required: ['signedTxns']
},
simulateRawTransactions: {
type: 'object',
properties: {
txns: {
type: 'array',
items: { type: 'string' }
}
},
required: ['txns']
},
simulateTransactions: {
type: 'object',
properties: {
txnGroups: {
type: 'array',
items: {
type: 'object',
properties: {
txns: {
type: 'array',
items: { type: 'object' }
}
},
required: ['txns']
}
},
allowEmptySignatures: { type: 'boolean', optional: true },
allowMoreLogging: { type: 'boolean', optional: true },
allowUnnamedResources: { type: 'boolean', optional: true },
execTraceConfig: { type: 'object', optional: true },
extraOpcodeBudget: { type: 'integer', optional: true },
round: { type: 'integer', optional: true }
},
required: ['txnGroups']
}
};
export class AlgodManager {
static readonly algodTools = [
{
name: 'compile_teal',
description: 'Compile TEAL source code',
inputSchema: algodToolSchemas.compileTeal,
},
{
name: 'disassemble_teal',
description: 'Disassemble TEAL bytecode back to source',
inputSchema: algodToolSchemas.disassembleTeal,
},
{
name: 'send_raw_transaction',
description: 'Submit signed transactions to the Algorand network',
inputSchema: algodToolSchemas.sendRawTransaction,
},
{
name: 'simulate_raw_transactions',
description: 'Simulate raw transactions',
inputSchema: algodToolSchemas.simulateRawTransactions,
},
{
name: 'simulate_transactions',
description: 'Simulate transactions with detailed configuration',
inputSchema: algodToolSchemas.simulateTransactions,
}
];
// Tool handlers
static async handleTool(name: string, args: Record<string, unknown>) {
try {
switch (name) {
case 'compile_teal':
if (!args.source || typeof args.source !== 'string') {
throw new McpError(ErrorCode.InvalidParams, 'TEAL source code is required');
}
const result = await AlgodManager.compile(args.source);
return {
content: [{
type: 'text',
text: JSON.stringify(result, null, 2),
}],
};
case 'disassemble_teal':
if (!args.bytecode || typeof args.bytecode !== 'string') {
throw new McpError(ErrorCode.InvalidParams, 'TEAL bytecode is required');
}
const disassembled = await AlgodManager.disassemble(args.bytecode);
return {
content: [{
type: 'text',
text: JSON.stringify(disassembled, null, 2),
}],
};
case 'send_raw_transaction':
if (!args.signedTxns || !Array.isArray(args.signedTxns)) {
throw new McpError(ErrorCode.InvalidParams, 'Signed transactions array is required');
}
const txns = args.signedTxns.map(txn => {
if (typeof txn !== 'string') {
throw new McpError(ErrorCode.InvalidParams, 'Each transaction must be a base64 string');
}
return Buffer.from(txn, 'base64');
});
const sent = await AlgodManager.sendRawTransaction(txns);
return {
content: [{
type: 'text',
text: JSON.stringify(sent, null, 2),
}],
};
case 'simulate_raw_transactions':
if (!args.txns || !Array.isArray(args.txns)) {
throw new McpError(ErrorCode.InvalidParams, 'Transactions array is required');
}
const rawTxns = args.txns.map(txn => {
if (typeof txn !== 'string') {
throw new McpError(ErrorCode.InvalidParams, 'Each transaction must be a base64 string');
}
return Buffer.from(txn, 'base64');
});
const simulated = await AlgodManager.simulateRawTransactions(rawTxns);
return {
content: [{
type: 'text',
text: JSON.stringify(simulated, null, 2),
}],
};
case 'simulate_transactions':
if (!args.txnGroups || !Array.isArray(args.txnGroups)) {
throw new McpError(ErrorCode.InvalidParams, 'Transaction groups array is required');
}
const simulateResult = await AlgodManager.simulateTransactions(args as any);
return {
content: [{
type: 'text',
text: JSON.stringify(simulateResult, null, 2),
}],
};
default:
console.error(`[MCP Error] Unknown tool requested: ${name}`);
throw new McpError(
ErrorCode.MethodNotFound,
`Unknown tool: ${name}`
);
}
} catch (error) {
if (error instanceof McpError) {
console.error(`[MCP Error] ${error.code}: ${error.message}`);
throw error;
}
console.error('[MCP Error] Unexpected error:', error);
throw new McpError(
ErrorCode.InternalError,
`Operation failed: ${error instanceof Error ? error.message : 'Unknown error'}`
);
}
}
/**
* Compiles TEAL source code to binary
* @param source TEAL source code as string or bytes
* @returns Compilation result with hash and bytecode
*/
static async compile(source: string | Uint8Array): Promise<CompileResponse> {
try {
// Convert string source to Buffer
if (typeof source === 'string') {
// Ensure proper line endings and add final newline
source = source.replace(/\r\n/g, '\n');
if (!source.endsWith('\n')) {
source += '\n';
}
source = new TextEncoder().encode(source);
}
const response = await algodClient.compile(source).do() as CompileResponse;
return response;
} catch (error) {
console.error('[MCP Error] Failed to compile TEAL:', error);
throw new McpError(
ErrorCode.InternalError,
`Failed to compile TEAL: ${error instanceof Error ? error.message : String(error)}`
);
}
}
/**
* Disassembles TEAL bytecode back to source
* @param bytecode TEAL bytecode as string or bytes
* @returns Disassembled TEAL source code
*/
static async disassemble(bytecode: string | Uint8Array): Promise<DisassembleResponse> {
try {
const response = await algodClient.disassemble(bytecode).do() as DisassembleResponse;
return response;
} catch (error) {
console.error('[MCP Error] Failed to disassemble TEAL:', error);
throw new McpError(
ErrorCode.InternalError,
`Failed to disassemble TEAL: ${error instanceof Error ? error.message : String(error)}`
);
}
}
/**
* Broadcasts signed transactions to the network
* @param signedTxns Single signed transaction or array of signed transactions
* @returns Transaction ID of the submission
*/
static async sendRawTransaction(signedTxns: Uint8Array | Uint8Array[]): Promise<PostTransactionsResponse> {
try {
const response = await algodClient.sendRawTransaction(signedTxns).do() as PostTransactionsResponse;
return response;
} catch (error) {
console.error('[MCP Error] Failed to send transaction:', error);
throw new McpError(
ErrorCode.InternalError,
`Failed to send transaction: ${error instanceof Error ? error.message : String(error)}`
);
}
}
/**
* Simulates raw transactions
* @param txns Single transaction or array of transactions to simulate
* @returns Simulation results
*/
static async simulateRawTransactions(txns: Uint8Array | Uint8Array[]): Promise<SimulateResponse> {
try {
const response = await algodClient.simulateRawTransactions(txns).do() as SimulateResponse;
return response;
} catch (error) {
console.error('[MCP Error] Failed to simulate raw transactions:', error);
throw new McpError(
ErrorCode.InternalError,
`Failed to simulate raw transactions: ${error instanceof Error ? error.message : String(error)}`
);
}
}
/**
* Simulates transactions with detailed configuration
* @param request Simulation request with transaction groups and configuration
* @returns Simulation results
*/
static async simulateTransactions(request: {
txnGroups: { txns: EncodedSignedTransaction[] }[];
allowEmptySignatures?: boolean;
allowMoreLogging?: boolean;
allowUnnamedResources?: boolean;
execTraceConfig?: SimulateTraceConfig;
extraOpcodeBudget?: number;
round?: number;
}): Promise<SimulateResponse> {
try {
const simulateRequest = new modelsv2.SimulateRequest({
txnGroups: request.txnGroups.map(group =>
new modelsv2.SimulateRequestTransactionGroup({ txns: group.txns })
),
allowEmptySignatures: request.allowEmptySignatures,
allowMoreLogging: request.allowMoreLogging,
allowUnnamedResources: request.allowUnnamedResources,
execTraceConfig: request.execTraceConfig,
extraOpcodeBudget: request.extraOpcodeBudget,
round: request.round,
});
const response = await algodClient.simulateTransactions(simulateRequest).do() as SimulateResponse;
return response;
} catch (error) {
console.error('[MCP Error] Failed to simulate transactions:', error);
throw new McpError(
ErrorCode.InternalError,
`Failed to simulate transactions: ${error instanceof Error ? error.message : String(error)}`
);
}
}
}